float a = 0;
while (true)
{
a++;
if (a > 16777216)
break; // Will never break... a stops at 16777216
}
谁能向我解释为什么在这段代码中浮点值在 16777216 处停止增加?
编辑:
或者更简单:
float a = 16777217; // a becomes 16777216
float a = 0;
while (true)
{
a++;
if (a > 16777216)
break; // Will never break... a stops at 16777216
}
谁能向我解释为什么在这段代码中浮点值在 16777216 处停止增加?
编辑:
或者更简单:
float a = 16777217; // a becomes 16777216
IEEE-754 浮点数(32 位)的简短总结:
(sign ? -1 : +1) * 2^exponent * (1.0 + mantissa)
1001 0000 0000 0000 0000 000 = 2^-1 + 2^-4 = .5 + .0625 = .5625
1.5625
现在以您的示例为例:
16777216 正好是 2 24,并且将表示为 32 位浮点数,如下所示:
10010111
)0 10010111 00000000000000000000000
(+1) * 2^24 * (1.0 + .0) = 2^24 = 16777216
现在让我们看看数字 16777217,或者正好是 2 24 +1:
(+1) * 2^24 * (1.0 + 2^-24) = 2^24 + 1 = 16777217
16777217 不能用浮点数精确表示。浮点数可以精确表示的下一个最大数字是 16777218。
因此,您尝试将浮点值 16777216 增加到 16777217,这无法以浮点数表示。
当您以二进制表示形式查看该值时,您会看到它是一个和多个零,即1 0000 0000 0000 0000 0000 0000
,或正好是 2^24。这意味着,在 16777216,这个数字刚刚增长了一位数。
由于它是一个浮点数,这可能意味着其末尾仍存储的最后一个数字(即在其精度范围内)也向左移动。
可能,您所看到的是精度的最后一位刚刚转移到大于 1 的值,因此添加一个不再有任何区别。
想象一下十进制形式。假设你有号码:
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