【问题标题】:Why does adding a small float to a large float just drop the small one?为什么将一个小浮点数添加到一个大浮点数只会删除一个小浮点数?
【发布时间】:2014-04-06 20:26:52
【问题描述】:

说我有:

float a = 3            // (gdb) p/f a   = 3
float b = 299792458    // (gdb) p/f b   = 299792448

然后

float sum = a + b      // (gdb) p/f sum = 299792448

我认为这与尾数移动有关。有人可以准确解释发生了什么吗? 32位

【问题讨论】:

  • 如果这是一个精度限制问题,您可能想尝试从浮点数更改为双精度数。记住,浮动四舍五入;如果您不希望这种行为,请坚持使用整数或长整数或使用扩展精度包之一。
  • 所以,我问的是四舍五入的机制
  • 想象浮点数是基于十位的,尾数只有三位数。那么 99900 就是 999*10^2。现在添加 3:99903。但尾数很短 -> 舍入。基于 2 的情况也是如此,但我们现在也看到了对转换的奇怪影响,因为我们以十进制打印。
  • 这里你有一个关于发生了什么的详细解释,或者换句话说,一个浮点数是如何存储到 32 位中的。 en.wikipedia.org/wiki/Single-precision_floating-point_format
  • 相关:Why adding big to small in floating point introduce more error? 用于类似情况,舍入误差与较小的操作数相比较大,但不等于它。

标签: c floating-point


【解决方案1】:

32-bit floats 只有 24 位精度。因此,浮点数不能准确地保持b - 它通过设置一些指数和尾数以尽可能接近1 来做最好的工作。 (最接近源中常量的可表示float;默认的 FP 舍入模式为“最近”。)

当您考虑ba 的浮点表示并尝试将它们相加时,加法运算将在尝试匹配b 时将小数a 的尾数向下移动s 指数,到值 (3) 从末尾下降的点,剩下的为 0。因此,加法运算符最终将浮点零添加到 b。 (这是一种过度简化;如果尾数部分重叠,低位仍然会影响舍入。)

一般来说,无限精度加法结果必须用当前的 FP 舍入模式四舍五入到最接近的 float,而这恰好等于 b

另请参阅Why adding big to small in floating point introduce more error?,了解数字发生一些变化但舍入误差较大的情况,例如使用十进制有效数字来帮助理解二进制浮点舍入。


脚注 1: 对于这么大的数字,最近的两个浮点数相距 32。现代clang甚至warns将源中的int四舍五入为代表不同值的float。除非您已经将其写为浮点或双精度常量(如 299792458.0f),在这种情况下,舍入会在没有警告的情况下发生。

这就是为什么将 sum 向上舍入到 299792480.0f 而不是向下舍入到 299792448.0f 的最小 a 值大约是 16.000001 对于舍入为 299792448.0fb 值。可运行示例on the Godbolt compiler explorer

默认的 FP 舍入模式舍入到最接近的偶数尾数作为抢七。 16.0 正好是一半,因此舍入到 0x4d8ef3c2 的位模式,而不是 0x4d8ef3c3。 https://www.h-schmidt.net/FloatConverter/IEEE754.html。任何略大于 16 的值都会向上舍入,因为舍入关心的是无限精度的结果。它实际上在添加之前不会移出位,这是一种过度简化。最接近 16.000001 的浮点数仅在其尾数中设置低位,位模式 0x41800001。它实际上约为 1.0000001192092896 x 24 或 16.0000019... 小得多,它会四舍五入到正好 16.0f 并且将是 b 的 b,因为b 的尾数已经是偶数了。


如果您通过使用double a,b 避免提前舍入,则当您使用float sum = a+b 时,您可以将299792480.0f 向上舍入而不是向下舍入到299792448.0f 的最小值约为a=6.0000001;,这是有道理的,因为整数值 ...58 保持为...58.0 而不是向下舍入到...48.0f,即float b = ...58 中的舍入误差为-10,因此a 可以小得多。

不过,这次有两个舍入步骤,a+b 舍入到最接近的 double,如果加法不准确,那么 double 舍入到 float。 (或者如果FLT_EVAL_METHOD == 2,比如 C 在 32 位 x86 上编译 80 位 x87 浮点,+ 结果将舍入到 80 位 long double,然后舍入到 float。)

【讨论】:

  • 精度是23还是24?
  • 23 个存储,1 个隐式,总共 24 个。
  • 标准是否保证这种行为在 C/C++ 中会发生?
  • @TStancek:对于承诺 IEEE-754 float 的 C/C++ 实现,或多或少取决于 FLT_EVAL_METHOD。尽管与 ISO C 指定的不同,GCC 甚至可以在语句之间保持额外的精度,而不仅仅是在单个表达式中,当为 32 位 x86 和 x87 FP(而不是 SSE/SSE2)构建目标时。
  • @Chris:我对这个答案进行了重要的编辑,比我开始编辑时打算写的要多。我最初只是要添加一个简短的示例,以说明在对齐尾数时“将位移出”并不是完全发生的事情。低位仍然对舍入很重要。但是解释其中的细节变成了一个很大的部分。如果您想减少此内容,请告诉我,我可以将我写的内容移至新答案。 (如果您想将其保留在您的答案中,那就太好了。)
【解决方案2】:

浮点数的精度有限。如果您使用的是float,那么您只使用了 32 位。但是,其中一些位保留用于定义指数,因此您实际上只有 23 位可供使用。你给的数字对于这 23 位来说太大了,所以最后几位被忽略了。

为了更直观一点,假设除 2 之外的所有位都为指数保留。然后我们可以毫无困难地表示 0、1、2 和 3,但是我们必须增加指数。现在我们需要用 2 位来表示 4 到 16。因此,可以表示的数字会有些分散:4 和 5 不会都存在。所以,4+1 = 4。

【讨论】:

    【解决方案3】:

    关于舍入机制,您真正需要了解的是,您得到的结果是最接近正确答案的浮点数(还有一些额外的规则来决定如果正确答案完全正确 在两个浮点数之间)。碰巧的是,您添加的较小数字小于该比例下两个浮点数之间距离的一半,因此结果与您添加的较大数字无法区分。这是正确,在浮点精度的范围内。如果您想要更好的答案,请使用精度更高的数据类型,例如 double

    【讨论】:

      【解决方案4】:

      另一个观点:Pigeon hole principle

      float 通常使用 32 位编码。因此只能对大约 232 个不同的值进行精确编码。
      299792458不是其中之一。

      float 通常被编码为 dyadic rational,其有效数为 24 位乘以 2 的幂次方。

      float b = 299792458;
      // b typically takes on the closest representable float: 299792480.0
      printf("%f\n", b); --> "299792448.000000"
      

      下一个较大的可表示浮点数是 299792480.0 或 32 距离。


      添加 299792448.0 + 3.0 是 299792451.0,但也不能完全编码为 float。根据当前的舍入模式(四舍五入),sum 又是 299792448.0。

      float a = 3;
      float sum = a + b
      printf("%f\n", sum); --> "299792448.000000"
      

      如果有a = 17;,那么总和 299792448.0 + 17.0 是 299792465.0 将四舍五入为 299792480.0

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-02-25
        • 2020-10-11
        • 1970-01-01
        • 2021-05-05
        • 2014-10-22
        • 1970-01-01
        • 1970-01-01
        • 2012-09-07
        相关资源
        最近更新 更多