Почему переменная float перестает увеличиваться на 16777216 в С#?

float a = 0;
while (true)
{
    a++;
    if (a > 16777216)
        break; // Will never break... a stops at 16777216
}

Может кто-нибудь объяснить это мне, почему значение float перестает увеличиваться на 16777216 в этом коде?

Edit:

Или еще проще:

float a = 16777217; // a becomes 16777216

Ответ 1

Короткий обзор чисел с плавающей запятой IEEE-754 (32-разрядный) с верхней части головы:

  • 1 бит (0 означает положительное число, 1 означает отрицательное число)
  • 8-разрядный показатель (с -127 смещением, неважно здесь)
  • 23 бит "мантисса"
  • С исключениями для значений экспоненты 0 и 255 вы можете вычислить значение как: (sign ? -1 : +1) * 2^exponent * (1.0 + mantissa)
    • Биты мантиссы представляют двоичные цифры после десятичного разделителя, например. 1001 0000 0000 0000 0000 000 = 2^-1 + 2^-4 = .5 + .0625 = .5625, а значение перед десятичным разделителем не сохраняется, но неявно принимается за 1 (если показатель равен 255, то предполагается, что 0, но это не важно здесь), поэтому для показателя 30, например, этот пример мантиссы представляет значение 1.5625

Теперь к вашему примеру:

16777216 - это ровно 2 24 и будет представлен как 32-битный float так:

  • sign = 0 (положительное число)
  • exponent = 24 (сохраняется как 24 + 127 = 151 = 10010111)
  • mantissa =.0
  • Как 32-битное представление с плавающей запятой: 0 10010111 00000000000000000000000
  • Следовательно: Value = (+1) * 2^24 * (1.0 + .0) = 2^24 = 16777216

Теперь посмотрим на номер 16777217 или ровно 2 24 +1:

  • Знак
  • и экспонент одинаковы
  • мантисса должна быть ровно 2 -24 так что (+1) * 2^24 * (1.0 + 2^-24) = 2^24 + 1 = 16777217
  • И здесь проблема. Мантисса не может иметь значение 2 -24 потому что оно имеет только 23 бита, поэтому число 16777217 просто не может быть представлено с точностью 32-битные числа с плавающей запятой!

Ответ 2

16777217 не может быть представлен точно с поплавком. Следующим самым большим числом, которое может представлять float, является 16777218.

Итак, вы пытаетесь увеличить значение с поплавком 16777216 до 16777217, которое не может быть представлено в поплавке.

Ответ 3

Когда вы посмотрите на это значение в своем двоичном представлении, вы увидите, что это одно и много нулей, а именно 1 0000 0000 0000 0000 0000 0000 или ровно 2 ^ 24. Это означает, что в 16777216 число только выросло на одну цифру.

Как это число с плавающей запятой, это может означать, что последняя цифра на ее конце, которая все еще сохраняется (то есть внутри ее точности), также сдвигается влево.

Вероятно, вы видите, что последняя цифра точности только что переместилась на нечто большее, чем одно, поэтому добавление одного не имеет никакого значения.

Ответ 4

Представьте это в десятичной форме. Предположим, у вас есть номер:

1.000000 * 10^6

или 1,000,000. Если бы все, что у вас было, было шесть цифр точности, добавив 0.5 к этому числу, получилось бы

1.0000005 * 10^6

Однако, текущее мышление с режимами округления fp заключается в использовании "Round to Even", а не "Round to Nearest". В этом случае каждый раз, когда вы увеличиваете это значение, оно будет округлено назад в блоке с плавающей точкой до 16 777 216 или 2 ^ 24. Синглы в IEE 754 представлены как:

+/- exponent (1.) fraction

где "1." подразумевается, и в этом случае доля составляет еще 23 бита, все нули. Дополнительный двоичный код 1 будет разливаться в цифру защиты, довести до этапа округления и удаляться каждый раз, независимо от того, сколько раз вы увеличиваете его. ulp или единица в последнем месте всегда будет равна нулю. Последнее успешное приращение:

+2^23 * (+1.) 11111111111111111111111 -> +2^24 * (1.) 00000000000000000000000