1

My sanity check fails because a double variable does not contain the expected result, it's really bizarre.

double a = 1117.54 + 8561.64 + 13197.37;
double b = 22876.55;
Console.WriteLine("{0} == {1}: {2}", a, b, a == b);

Gives us this output:

22876.55 == 22876.55: False

Further inspection shows us that variable a, in fact, contains the value 22876.550000000003.

This is reproducible in vb.net as well. Am I sane? What is going on?

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
invert
  • 2,016
  • 14
  • 20
  • 1
    Floating point types can't always represent the *exact* decimal value. It's either a "known bug" or "by design", depending on your perspective. Try using the `decimal` type instead. – Cody Gray - on strike Mar 01 '11 at 07:18
  • 1
    You're sane, but your expectations are inappropriate. Read http://csharpindepth.com/Articles/General/FloatingPoint.aspx – Jon Skeet Mar 01 '11 at 07:21
  • @Cody Okay decimal may be a workaround. But how can I mitigate any other possible such inconsistencies, in all the other code in all the other apps already written? – invert Mar 01 '11 at 07:24
  • possible duplicate of [Why don't operations on double-precision values give expected results?](http://stackoverflow.com/questions/3892908/why-dont-operations-on-double-precision-values-give-expected-results) And LOTS of other questions! – dan04 Mar 01 '11 at 07:25
  • @Wesley: You can't. No one else can either. That's just how computers work; its a consequence of the internal representation of floating point types. Read the article Jon's comment linked to, if you really care about this sort of thing. The bottom line is that you shouldn't expect to be able to compare the result of a floating point operation to a floating point literal. – Cody Gray - on strike Mar 01 '11 at 07:25
  • _Now_ all the related questions appear, thanks for the link @Jon, and @Cody I guess I should know better, now I do! – invert Mar 01 '11 at 07:28
  • 1
    I suspect the related questions all appear now because of the tag dan04 added: "floating-accuracy". It is a known phenomenon, and certainly the source of uncountable (heh!) bugs if you aren't aware of it. – Cody Gray - on strike Mar 01 '11 at 07:30
  • @Cody Your `decimal` type suggestion is the most effective in this case, If you post an answer I can accept it (unless we get voted to close) -- not like me to post dups! – invert Mar 01 '11 at 07:35
  • @Cody/Wesley: It depends on what the data represent. If they're dollars and cents, then `decimal` is the appropriate type. If they're physical measurements, then no. – dan04 Mar 01 '11 at 07:37
  • @Wesley: Answer posted. @dan04: Agreed; I tried to address that more carefully in my answer. – Cody Gray - on strike Mar 01 '11 at 07:43
  • They are monetary: accounting system integration. The system's SDK used to work in cents only (ints), and suddenly switched to doubles. Thanks for all the great replies! – invert Mar 01 '11 at 08:20

6 Answers6

3

Floating point types are not always capable of accurately representing their exact decimal values. It's either a "known bug" or "by design", depending on your perspective. Either way, it's a consequence of the internal representation of floating point types, and a common source of bugs.

The problem is nearly unavoidable, too, short of writing a complex computer algebra system that represents values symbolically, rather than as numeric types. Open up Windows Calculator, determine the square root of 4, and then subtract 2 from that value. You'll get some nonsensical floating point number that is incredibly close to 0, but not exactly 0. The result of your square root computation wasn't stored as exactly 2, so when you subtract exactly 2 from it, you get an "unexpected" result. Unexpected, that is, unless you know the dirty little secret about base 2 arithmetic.

If you're curious, there are several places you might go to find out more information about why this is the case. Jon Skeet wrote an article explaining binary floating point operations in the context of the .NET Framework. If you have the time, you should also peruse the aptly-named publication, What Every Computer Scientist Should Know About Floating-Point Arithmetic.

But the bottom line is that you shouldn't expect to be able to compare the result of a floating point operation to a floating point literal. In this specific case, you might try using the decimal type, instead. It's not really a "solution" (see the other answers for those, scary mathematical concepts like epsilons), but the results are often more predictable, as the decimal type is better at accurately representing base 10 numbers (such as those used in currency and financial calculations).

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
1

That's floating point rounding and this is by design - you should never expect a floating point variable to be precisely equal to any other floating point variable except a set of special cases that are quite rare.

Also see this question.

Community
  • 1
  • 1
sharptooth
  • 167,383
  • 100
  • 513
  • 979
  • I fail to see how this is by (intentional) design, thats really crazy. – invert Mar 01 '11 at 07:22
  • 1
    @Wesley it's by "intentional" design because it is impossible to represent a potentially infinitely long decimal value with a finite number of bits. – Sapph Mar 01 '11 at 07:24
  • 1
    Emphasis on *bits*. `doubles` are stored in base 2, in which 0.1 decimal is 0.0 0011 0011 0011 0011... repeating. – dan04 Mar 01 '11 at 07:28
  • Thanks @Sharptooth, from today I will definitely handle floats with more attention :) – invert Mar 01 '11 at 08:22
1

As sharptooth said, it is how variables with floating point are saved in memory. You can read more here

Also to check if two floating numbers are equal you can use something like this:

double a = 1117.54 + 8561.64 + 13197.37;
double b = 22876.55;
Console.WriteLine("{0} == {1}: {2}", a, b, fabs(a-b) < 1e-9);

What this does it checks by how much different are these numbers. If their difference is only after 9th digit after point, you can presume they are equal. To get more precision just use lower epsilon (maximum difference between 2 numbers for them to be considered equal).

enoyhs
  • 2,049
  • 2
  • 16
  • 17
1

You're sane. You're just dealing with numbers that can't be stored perfectly in an arbitrary number of binary digits. You'll see this in any language - the rounding "errors" are inherent in the floating point format, and thus in the hardware, too.

If you really need perfect comparisons, try using the decimal trick: Pick the smallest fraction you'll be dealing with, and express everything in terms of that. Your own personal Planck's constant, if you will. Your example code, for instance, would become:

int a = 111754 + 856164 + 1319737;
int b = 2287655;
//Convert back to decimal format for human consumption:
Console.WriteLine("{0} == {1}: {2}", ((double)a)/100, ((double)b)/100, a == b);

Hope this helps!

Xavier Holt
  • 14,471
  • 4
  • 43
  • 56
  • @Wesley Welcome! It's based on how databases store numbers like this (often used for currency, where rounding errors would be rather unfortunate). And if you go with this method, keep in mind that every digit of precision after the decimal takes away a digit before the decimal - if your numbers are very big as well as very precise, you might need to use something bigger than an int to fit them safely. Cheers! – Xavier Holt Mar 01 '11 at 07:50
  • Funnily enough, it's for an accounting system that used to work off this numbering system, but the SDK I use changed over to use doubles, without warning. Needless to say this has caused much trouble. Shocking as its a major player too! :p – invert Mar 01 '11 at 08:18
1

Floating point stores an approximation to the number (about 6-7 decimal places of accuracy for a float).

When you calculate with fp numbers you therefore often end up with tiny representational errors in each number which are carried into the calculation. Operations such as multiplying then magnify those errors. If you are not careful, the errors can become significant.

The most common problem with this is using == to determine if two values are precisely equal, because 2.999999 and 3.00000000 are very close, but not equal. Due to the error in fp representation, it is very common to end up with numbers that are close but not equal, as you have found.

Therefore, instead of saying "is my number exactly equal to 3.0", we have to say "is my number near enough to 3.0 that I am happy with it?". We do this by testing with a tolerance value, as in: "Is my value is greater than 2.999 and smaller than 3.001". When writing the number out, you can use a format string like "{0:0.000}" to round it and remove the tiny error it is displaying.

so you can achieve what you want (to 3 decimal places accuracy) with something like:

Console.WriteLine("{0:0.000} == {1:0.000}: {2}", a, b, Math.Abs(a - b) < 0.0001);
Jason Williams
  • 56,972
  • 11
  • 108
  • 137
  • Actually it's 7-8 decimal digits or more exact 24 binary digits which is equivalent to all integers in the range 0-9999999 and those in the range 10000000-16277215. This latter range is where the "-8" digits come in. The sign of the number does not affect precision. – Olof Forshell Mar 23 '11 at 10:13
0

Use Decimal data-type instead of Double data-type. Numeric real literal to be treated as decimal, use the suffix m or M. Without the suffix m, the number is treated as a double and generates a compiler error.

decimal a = 1117.54M + 8561.64M + 13197.37M;
decimal b = 22876.55M;
Console.WriteLine("{0} == {1}: {2}", a, b, a == b);