问与答 浮点数-为什么C#中的float变量在16777216处停止递增?

john · 2020-03-07 13:27:07 · 热度: 99
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 条回复
wright #1 · 2020-03-07 13:27:07

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

  • 1位符号(0表示正数,1表示负数)
  • 8位指数(偏置为-127,此处不重要)
  • 23位“尾数”
  • 除指数值0和255以外,您可以将其计算为:(+1) * 2^24 * (1.0 + 2^-24) = 2^24 + 1 = 16777217
    • 尾数位代表小数点分隔符之后的二进制数字,例如 (+1) * 2^24 * (1.0 + 2^-24) = 2^24 + 1 = 16777217,并且不存储小数点分隔符前面的值,而是隐式假定为1(如果指数为255,则假定为0,但这在这里并不重要),因此对于指数为30的情况,例如,此尾数示例表示该值 1.5625

现在来看您的示例:

16777216就是224,将被表示为32位浮点数,如下所示:

  • 符号= 0(正数)
  • 指数= 24(存储为24 + 127 = 151 = (+1) * 2^24 * (1.0 + 2^-24) = 2^24 + 1 = 16777217
  • 尾数= .0
  • 作为32位浮点表示形式:(+1) * 2^24 * (1.0 + 2^-24) = 2^24 + 1 = 16777217
  • 因此:值= (+1) * 2^24 * (1.0 + 2^-24) = 2^24 + 1 = 16777217

现在让我们看一下数字16777217,或者确切地说是224 + 1:

  • 符号和指数相同
  • 尾数必须恰好是2-24,以便(+1) * 2^24 * (1.0 + 2^-24) = 2^24 + 1 = 16777217
  • 这就是问题所在。 尾数不能有2-24的值,因为它只有23位,所以数字16777217不能用32位浮点数的精度表示!
jorge #2 · 2020-03-07 13:27:09

16777217不能完全用浮点数表示。 浮点数可以准确表示的下一个最高数字是16777218。

因此,您尝试将浮点值16777216增加到16777217,这不能用浮点表示。

trayden #3 · 2020-03-07 13:27:10

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

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

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

cliff #4 · 2020-03-07 13:27:11

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

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
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册