Why does a float variable stop incrementing at 16777216 in C#?
float a = 0;
while (true)
{
a++;
if (a > 16777216)
break; // Will never break... a stops at 16777216
}
Can anyone explain this to me why a float value stops incrementing at 16777216 in this code?
Edit:
Or even more simple:
float a = 16777217; // a becomes 16777216
Short roundup of IEEE-754 floating point numbers (32-bit) off the top of my head:
- 1 bit sign (0 means positive number, 1 means negative number)
- 8 bit exponent (with -127 bias, not important here)
- 23 bits "mantissa"
- With exceptions for the exponent values 0 and 255, you can calculate the value as:
(sign ? -1 : +1) * 2^exponent * (1.0 + mantissa)
- The mantissa bits represent binary digits after the decimal separator, e.g.
1001 0000 0000 0000 0000 000 = 2^-1 + 2^-4 = .5 + .0625 = .5625
and the value in front of the decimal separator is not stored but implicitly assumed as 1 (if exponent is 255, 0 is assumed but that's not important here), so for an exponent of 30, for instance, this mantissa example represents the value1.5625
- The mantissa bits represent binary digits after the decimal separator, e.g.
Now to your example:
16777216 is exactly 224, and would be represented as 32-bit float like so:
- sign = 0 (positive number)
- exponent = 24 (stored as 24+127=151=
10010111
) - mantissa = .0
- As 32 bits floating-point representation:
0 10010111 00000000000000000000000
- Therefore: Value =
(+1) * 2^24 * (1.0 + .0) = 2^24 = 16777216
Now let's look at the number 16777217, or exactly 224+1:
- sign and exponent are the same
- mantissa would have to be exactly 2-24 so that
(+1) * 2^24 * (1.0 + 2^-24) = 2^24 + 1 = 16777217
- And here's the problem. The mantissa cannot have the value 2-24 because it only has 23 bits, so the number 16777217 just cannot be represented with the accuracy of 32-bit floating points numbers!
16777217 cannot be represented exactly with a float. The next highest number that a float can represent exactly is 16777218.
So, you try to increment the float value 16777216 to 16777217, which cannot be represented in a float.
When you look at that value in its binary representation, you'll see that it's a one and many zeroes, namely 1 0000 0000 0000 0000 0000 0000
, or exactly 2^24. That means, at 16777216, the number has just grown by one digit.
As it's a floating point number, this can mean that the last digit at its end that is still stored (i.e. within its precision) is shifted to the left, as well.
Probably, what you're seeing is that the last digit of precision has just shifted to something bigger than one, so adding one doesn't make any difference any more.
Imagine this in decimal form. Suppose you had the number:
1.000000 * 10^6
or 1,000,000. If all you had were six digits of accuracy, adding 0.5 to this number would yield
1.0000005 * 10^6
However, current thinking with fp rounding modes is to use "Round to Even," rather than "Round to Nearest." In this instance, every time you increment this value, it will round back down in the floating point unit back to 16,777,216, or 2^24. Singles in IEE 754 is represented as:
+/- exponent (1.) fraction
where the "1." is implied and the fraction is another 23 bits, all zeros, in this case. The extra binary 1 will spill into the guard digit, carry down to the rounding step, and be deleted each time, no matter how many times you increment it. The ulp
or unit in the last place will always be zero. The last successful increment is from:
+2^23 * (+1.) 11111111111111111111111 -> +2^24 * (1.) 00000000000000000000000