Why does a float variable stop incrementing at 16777216 in C#?

C#Floating Point

C# Problem Overview


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

C# Solutions


Solution 1 - C#

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 value 1.5625

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!

Solution 2 - C#

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.

Solution 3 - C#

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.

Solution 4 - C#

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJanView Question on Stackoverflow
Solution 1 - C#AndiDogView Answer on Stackoverflow
Solution 2 - C#Eric J.View Answer on Stackoverflow
Solution 3 - C#O. R. MapperView Answer on Stackoverflow
Solution 4 - C#MaxView Answer on Stackoverflow