1

I have been trying to solve a bug that was caused by floating point arithmetic and I reduced it to a simple piece of code that is causing the behavior I don't understand:

float one = 1;
float three = 3;

float result = one / three;
Console.WriteLine(result); // prints 0.33333

double back = three * result;

if (back > 1.0)
    Console.WriteLine("larger than one");
else if (back < 1.0)
    Console.WriteLine("less than one");
else
    Console.WriteLine("exactly one");

As result rounded to 0.33333, I would expect back to be less that 1, however the output is "larger than one".

Can someone explain what is going on here?

Qantas 94 Heavy
  • 15,750
  • 31
  • 68
  • 83
korhner
  • 723
  • 4
  • 14
  • Rounding. Presumably to get that result, the 1/3 in FP is more like 0.333...34, where the 4 is outside the printed range – Marc Gravell Feb 15 '14 at 08:40
  • possible duplicate of [Comparing double values in C#](http://stackoverflow.com/questions/1398753/comparing-double-values-in-c-sharp) – Athari Feb 15 '14 at 08:41
  • But why would 0.33333... round to 0.33334? Shouldn't it round to 0.3333 as 3 is less than 5? – korhner Feb 15 '14 at 08:41
  • 2
    @korhner FP rounding is to the nearest *representable value*. It is not like decimal rounding. – Marc Gravell Feb 15 '14 at 09:13

2 Answers2

4

When I tried above code I found that

float result = one / three;

statement evaluate the value of result as 0.333333343 not the 0.33333 but console prints it as 0.33333 and then I executed the following statement

double back = three * result;

it evaluates the back as 1.0000000298023224 which is obviously greater than 1 that's why you are getting "larger than one".

Sachin
  • 40,216
  • 7
  • 90
  • 102
  • 1
    @korhner `float` and `double` do indeed use base 2, and `decimal` does use base 10, but all three of those are floating point types. They're called "floating point" because (roughly) `1.23m` is stored as `123`, plus the position of the `.`. `12.3m` is also stored as `123`, plus a different position of the `.`. The fact that the position of the `.` can vary makes the type a floating point type, hence the name. This is different from fixed-point types, where (for example) some languages' `Currency` type always stores *exactly* four decimals. –  Feb 15 '14 at 09:40
4

Using IEEE 754 rounding, let's see what's going on.

In IEEE 754 single-precision floating point, the value of a finite number is dictated by the following:

-1sign × 2exponent × (1 + mantissa × 2-23)

Where

  • sign is 0 if positive, otherwise 1;
  • exponent is a value between -126 and 127 (-127 and 128 are special); and
  • mantissa is a value between 0 and 8388607 (because it's a 23 bit integer).

If we substitute sign with 0 and exponent with -2, then we're guaranteed a value between 0.25 and 0.5. Why?

1 × 2-2

is ¼. The value of

1 + mantissa × 2-23

is guaranteed to be between 1 and 2, so that's our sign and exponent sorted.


Moving on, we can work out fairly quickly that there are two values which can be used as the mantissa value: 2796202 and 2796203.

Substituting each in, we get the following two values (one lower, one higher):

  • 0.333333313465118408203125 (for mantissa = 2796202)
  • 0.3333333432674407958984375 (for mantissa = 2796203)

The binary representation of the exact value (up to 22 digits) is:

1010101010101010101010...

As the next digit would be 1, that would mean the value rounds up, not down. For this reason, the higher one has a less significant error than the lower one:

  • 0.333333313465118408203125 - ⅓ ≑ -1.987 × 10-8
  • 0.3333333432674407958984375 - ⅓ ≑ 9.934 × 10-9

And since it's larger than the exact value, when multiplied back it will be more than 1. That's why it uses a value that appears off initially -- binary rounding sometimes goes in the opposite direction of decimal rounding.

Qantas 94 Heavy
  • 15,750
  • 31
  • 68
  • 83
  • 1
    Note that if you change "double back = three * result;" to "float back = three * result;" the answer is "exactly one". That would mask the fact that the float version of 1/3 is greater than 1/3. – Rick Regan Feb 15 '14 at 14:56