56

If I execute the following expression in C#:

double i = 10*0.69;

i is: 6.8999999999999995. Why?

I understand numbers such as 1/3 can be hard to represent in binary as it has infinite recurring decimal places but this is not the case for 0.69. And 0.69 can easily be represented in binary, one binary number for 69 and another to denote the position of the decimal place.

How do I work around this? Use the decimal type?

sth
  • 222,467
  • 53
  • 283
  • 367
Dan
  • 29,100
  • 43
  • 148
  • 207
  • 97
    Please consider this: What are the chances that you stumbled upon a bug like this in a framework that has been around for years? – Brian Rasmussen Sep 14 '09 at 10:33
  • 21
    You'll get a similar result in almost any other language/framework that correctly implements floating-point arithmetic. – LukeH Sep 14 '09 at 10:36
  • 33
    Heh heh. It's a immutable truth; anytime you read a topic with '... multiplication broken in abc language ... ' it will be about floating point :) – Noon Silk Sep 14 '09 at 10:36
  • Well it was slightly tongue in cheek. None the less its an interesting problem. – Dan Sep 14 '09 at 10:53
  • 7
    Brian: I don't think that's a good approach. I think you can reasonably assume there may be a problem, but I also think it is reasonable to research into it yourself. Back in the day I remember finding Java bugs; it doesn't mean I'm special, it's just an accident. Research is what needs to be taught. – Noon Silk Sep 14 '09 at 11:12
  • 1
    Bugs in the .NET framework? Don't be ridiculous: http://support.microsoft.com/kb/867460 Although one with floating point arithmetic would be a big one – Chris S Sep 14 '09 at 11:36
  • Link above should be http://support.microsoft.com/kb/945757 – Chris S Sep 14 '09 at 11:37
  • "If you think you've found a bug in {compiler,operating system,framework}, You're Wrong." –  Oct 29 '09 at 09:44
  • 3
    0.69 is not exactly representable in binary: It's expansion is something like 1/2+1/8+1/16+1/512+1/2048+1/32768+... – erikkallen Nov 03 '09 at 12:55
  • 23
    i like how the explanation of how to store it in BINARY relies on specifying where the DECIMAL place is – jk. Mar 18 '10 at 09:20
  • Dear question maker, I think your expectation is right. 10 * 0.69 is 6.9 Computer scientists made mistake, and ruins the world, brings suffer and pain with this. – Estevez May 23 '16 at 09:43
  • @jk. theoretically it can be generalized to the [radix point](https://en.wikipedia.org/wiki/Radix_point), though – Salem May 27 '17 at 19:09
  • The answer to the original question is YES. IEEE754 64-bit binary floating point, which virtually all CPUs implement, is broken for decimal arithmetic. That's why .NET also offers the `decimal` type. – TimTIM Wong Sep 11 '21 at 17:34
  • With a minor change, the results are different: 10*0.69m == 6.90. So using decimal instead of double gives the correct result. – cskwg Feb 17 '22 at 07:03

6 Answers6

159

Because you've misunderstood floating point arithmetic and how data is stored.

In fact, your code isn't actually performing any arithmetic at execution time in this particular case - the compiler will have done it, then saved a constant in the generated executable. However, it can't store an exact value of 6.9, because that value cannot be precisely represented in floating point point format, just like 1/3 can't be precisely stored in a finite decimal representation.

See if this article helps you.

Community
  • 1
  • 1
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 9
    I might be showing my naivety, but why doesn't the framework work around this and hide this problem from me and give me the right answer,0.69!!! – Dan Sep 14 '09 at 10:51
  • 37
    @Dan: Because you might not *mean* 0.69. You might mean any of the other many values which would be represented by the same bit pattern. If you want to represent values which are fundamentally decimal in nature, you should use the `decimal` type to start with. Note that choosing the "output" here is a matter of working out how to convert an instance of a particular type into a *text* format... that's somewhat separate from deciding which type to use in the first place. – Jon Skeet Sep 14 '09 at 11:01
  • 61
    Dan: because "double" is computerese for "I'd rather have it done fast than done right." – Robert L Sep 14 '09 at 11:20
  • 1
    @Jon: Maybee you should consider adding your name to the article. It gets additional credibility that way. And maybe also add an date that it was written (in case binary floating point implementation changes in future :)). – pero Sep 14 '09 at 11:46
  • 1
    I could possibly add my name at the bottom of all my articles. Will consider it. As for dating it - I don't think that's necessary. – Jon Skeet Sep 14 '09 at 12:51
  • @RobertL, I realize I am "way late to this party" but your comment is my absolute favorite! – Andrew Steitz Feb 21 '13 at 20:43
  • @RobertL I realize it was tongue in cheek, but... double is just as fine as decimal for certain kinds of calculations. It's just that it will not tend to match with the calculations you do in decimal with pen & paper :D – Luaan May 20 '15 at 12:27
  • So what is the solution? – Estevez May 23 '16 at 09:43
  • @Estevez: To what specific task? I've explained why the result is what it is, but in order to have a "solution" there'd have to be a particular goal. In many cases the answer is "don't use `double` if you care about precise decimal representations". – Jon Skeet May 23 '16 at 09:52
  • 6.9 in binary is `110.11100110011001100...` and in IEEE 754 32-bit single precision: `0-10000001-10111001100110011001100` – ReinstateMonica3167040 Jan 21 '18 at 15:11
53

why doesn't the framework work around this and hide this problem from me and give me the right answer,0.69!!!

Stop behaving like a dilbert manager, and accept that computers, though cool and awesome, have limits. In your specific case, it doesn't just "hide" the problem, because you have specifically told it not to. The language (the computer) provides alternatives to the format, that you didn't choose. You chose double, which has certain advantages over decimal, and certain downsides. Now, knowing the answer, you're upset that the downsides don't magically disappear.

As a programmer, you are responsible for hiding this downside from managers, and there are many ways to do that. However, the makers of C# have a responsibility to make floating point work correctly, and correct floating point will occasionally result in incorrect math.

So will every other number storage method, as we do not have infinite bits. Our job as programmers is to work with limited resources to make cool things happen. They got you 90% of the way there, just get the torch home.

Russell Steen
  • 6,494
  • 6
  • 38
  • 56
  • 2
    Well admittedly I don't have a deep understanding of this. But I was thinking there must be some sort of work around to this, such as using some alternate notation, it seems to be the number 69 its easily storable in binary, as well as the position of a decimal point. – Dan Sep 14 '09 at 11:11
  • 19
    @Dan: and that, essentially (I believe), is what a Decimal does. So yes, you should have used that if you wanted an exact answer. – Dan Tao Sep 14 '09 at 11:15
  • 1
    (much confusion over Dan's) - Yes Decimal does solve that particular problem. It adds some of it's own. Without knowing the details of the problem, it's hard to say which is "Correct" for you. It may very well be that the "correct" solution to your problem is to store a Double and round it off for your users. – Russell Steen Sep 14 '09 at 11:27
  • 5
    @Dan: The decimal place is stored in binary. So it comes after a position like 1/2, 1/4, 1/8, 1/16, 1/32, etc. – Sam Harwell Sep 14 '09 at 14:55
  • Read http://binary-system.base-conversion.ro/convert-real-numbers-from-decimal-system-to-32bit-single-precision-IEEE754-binary-floating-point.php to see why a computer shows you that! – ReinstateMonica3167040 Jan 21 '18 at 15:13
16

And 0.69 can easily be represented in binary, one binary number for 69 and another to denote the position of the decimal place.

I think this is a common mistake - you're thinking of floating point numbers as if they are base-10 (i.e decimal - hence my emphasis).

So - you're thinking that there are two whole-number parts to this double: 69 and divide by 100 to get the decimal place to move - which could also be expressed as:
69 x 10 to the power of -2.

However floats store the 'position of the point' as base-2.

Your float actually gets stored as:
68999999999999995 x 2 to the power of some big negative number

This isn't as much of a problem once you're used to it - most people know and expect that 1/3 can't be expressed accurately as a decimal or percentage. It's just that the fractions that can't be expressed in base-2 are different.

Keith
  • 150,284
  • 78
  • 298
  • 434
  • We might just need to say "recurring binary places" and "move the binary point" to get the idea across. – codewarrior Oct 25 '11 at 12:03
  • "68999999999999995 x 2 to the power of some big negative number" is not true. The mantissa does not look like 690(...)0; it's something different entirely. – Nayuki Apr 21 '15 at 23:27
  • @NayukiMinase yeah, I know, I've just tried to simplify it for the explanation. The real mantissa will look like an unrelated number and be in binary. This is concept people get in decimal (for instance 1/3 being 0.333...) but don't realise applies to different numbers in binary (for instance 1.1) – Keith Apr 23 '15 at 12:37
12

but why doesn't the framework work around this and hide this problem from me and give me the right answer,0.69!!!

Because you told it to use binary floating point, and the solution is to use decimal floating point, so you are suggesting that the framework should disregard the type you specified and use decimal instead, which is very much slower because it is not directly implemented in hardware.

A more efficient solution is to not output the full value of the representation and explicitly specify the accuracy required by your output. If you format the output to two decimal places, you will see the result you expect. However if this is a financial application decimal is precisely what you should use - you've seen Superman III (and Office Space) haven't you ;)

Note that it is all a finite approximation of an infinite range, it is merely that decimal and double use a different set of approximations. The advantage of decimal is it produces the same approximations that you would if you were performing the calculation yourself. For example if you calculated 1/3, you would eventually stop writing 3's when it was 'good enough'.

Clifford
  • 88,407
  • 13
  • 85
  • 165
8

For the same reason that 1 / 3 in a decimal systems comes out as 0.3333333333333333333333333333333333333333333 and not the exact fraction, which is infinitely long.

erikkallen
  • 33,800
  • 13
  • 85
  • 120
  • Read http://binary-system.base-conversion.ro/convert-real-numbers-from-decimal-system-to-32bit-single-precision-IEEE754-binary-floating-point.php to learn how computers round it! – ReinstateMonica3167040 Jan 21 '18 at 15:12
7

To work around it (e.g. to display on screen) try this:

double i = (double) Decimal.Multiply(10, (Decimal) 0.69);

Everyone seems to have answered your first question, but ignored the second part.

Richard
  • 14,798
  • 21
  • 70
  • 103