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

谁能向我解释为什么在这段代码中浮点值在 16777216 处停止增加?

编辑:

或者更简单:

float a = 16777217; // a becomes 16777216
4

4 回答 4

69

IEEE-754 浮点数(32 位)的简短总结:

  • 1 位符号(0 表示正数,1 表示负数)
  • 8 位指数(有 -127 偏差,这里不重要)
  • 23 位“尾数”
  • 除了指数值 0 和 255 之外,您可以将值计算为:(sign ? -1 : +1) * 2^exponent * (1.0 + mantissa)
    • 尾数位表示小数分隔符后的二进制数字,例如,小数分隔符前面的值不存储,但隐式假定为 1(如果指数为 255,则假定为 0,但这并不重要),因此对于30,例如,这个尾数示例表示值1001 0000 0000 0000 0000 000 = 2^-1 + 2^-4 = .5 + .0625 = .56251.5625

现在以您的示例为例:

16777216 正好是 2 24,并且将表示为 32 位浮点数,如下所示:

  • 符号 = 0(正数)
  • 指数 = 24(存储为 24+127=151= 10010111
  • 尾数 = .0
  • 作为 32 位浮点表示:0 10010111 00000000000000000000000
  • 因此:价值=(+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 位浮点数的精度来表示!
于 2012-09-26T09:15:01.640 回答
17

16777217 不能用浮点数精确表示。浮点数可以精确表示的下一个最大数字是 16777218。

因此,您尝试将浮点值 16777216 增加到 16777217,这无法以浮点数表示。

于 2012-09-26T07:33:41.493 回答
13

当您以二进制表示形式查看该值时,您会看到它是一个和多个零,即1 0000 0000 0000 0000 0000 0000,或正好是 2^24。这意味着,在 16777216,这个数字刚刚增长了一位数。

由于它是一个浮点数,这可能意味着其末尾仍存储的最后一个数字(即在其精度范围内)也向左移动。

可能,您所看到的是精度的最后一位刚刚转移到大于 1 的值,因此添加一个不再有任何区别。

于 2012-09-26T07:34:05.527 回答
4

想象一下十进制形式。假设你有号码:

1.000000 * 10^6

或 1,000,000。如果你只有六位数的准确度,那么在这个数字上加上 0.5 就会得到

1.0000005 * 10^6

但是,当前使用 fp 舍入模式的想法是使用“舍入到偶数”而不是“舍入到最近”。在这种情况下,每次增加此值时,它都会以浮点单位向下舍入回 16,777,216 或 2^24。IEE 754 中的单曲表示为:

+/- exponent (1.) fraction

“1”在哪里。是隐含的,在这种情况下,分数是另外 23 位,全为零。额外的二进制 1 将溢出到保护位,向下执行舍入步骤,并且每次都被删除,无论您增加多少次。最后一个位置的ulp单位将始终为零。最后一次成功的增量来自:

+2^23 * (+1.) 11111111111111111111111 -> +2^24 * (1.) 00000000000000000000000
于 2013-11-03T02:13:06.550 回答