为什么 10/3 在 C 中是准确的?

Why 10/3 it's exact in C?

看看这段代码。 10/3 return 3.3333332538604736328125000 当我在计算器中乘以 3 时,我得到 9.99,但如果按代码执行相同操作,我正好得到 10.00。 怎么可能?

#include <stdlib.h>
#include <stdio.h>

int main() {

    float v = 10.f/3.f;
    float test = v*3.f;
    printf("10/3 => %25.25f \n (10/3)*3 => %25.25f\n",v,test);
    return 0;
}

这是没有 printf 的汇编代码,使用默认的 gcc 7.2.1 参数编译:

0000000000400497 <main>:
  400497:       55                      push   rbp
  400498:       48 89 e5                mov    rbp,rsp
  40049b:       f3 0f 10 05 b1 00 00    movss  xmm0,DWORD PTR [rip+0xb1]        # 400554 <_IO_stdin_used+0x4>
  4004a2:       00 
  4004a3:       f3 0f 11 45 fc          movss  DWORD PTR [rbp-0x4],xmm0
  4004a8:       f3 0f 10 4d fc          movss  xmm1,DWORD PTR [rbp-0x4]
  4004ad:       f3 0f 10 05 a3 00 00    movss  xmm0,DWORD PTR [rip+0xa3]        # 400558 <_IO_stdin_used+0x8>
  4004b4:       00 
  4004b5:       f3 0f 59 c1             mulss  xmm0,xmm1
  4004b9:       f3 0f 11 45 f8          movss  DWORD PTR [rbp-0x8],xmm0
  4004be:       b8 00 00 00 00          mov    eax,0x0
  4004c3:       5d                      pop    rbp
  4004c4:       c3                      ret    
  4004c5:       66 2e 0f 1f 84 00 00    nop    WORD PTR cs:[rax+rax*1+0x0]
  4004cc:       00 00 00 
  4004cf:       90                      nop

我认为 mulss 具有 CPU 特征。

请注意,GNU BC 程序中的 10/3 returns 3.3333333333333333333333 ( *3 => 9.9999) 和 SciLab 中的 returns 3.3333333333333334813631 ( *3 => 10).

您最终得到的结果恰好是 10,因为表示恰好是这样计算的。我对 float 和 double 的实现都是一样的。

让我们看一个使用 double 的例子:

如果我们使用 %a 以十六进制浮点表示法打印出 10./3.,我们会得到:

0x1.aaaaaaaaaaaabp+1

这与 IEEE754 双重表示匹配 0x401aaaaaaaaaaaab

以上数字归一化后为:

0x3.5555555555558

二进制:

11.0101010101010101010101010101010101010101010101011

为简单起见,我们将乘以 3 而不是乘以 3:

     11.0101010101010101010101010101010101010101010101011
+    11.0101010101010101010101010101010101010101010101011
---------------------------------------------------------
    110.1010101010101010101010101010101010101010101010111
+    11.0101010101010101010101010101010101010101010101011
---------------------------------------------------------
   1010.0000000000000000000000000000000000000000000000000

正好是 10。

编辑:

看来我把最后几位的数学搞砸了。实际总和:

     11.0101010101010101010101010101010101010101010101011
+    11.0101010101010101010101010101010101010101010101011
---------------------------------------------------------
    110.1010101010101010101010101010101010101010101010110
+    11.0101010101010101010101010101010101010101010101011
---------------------------------------------------------
   1010.0000000000000000000000000000000000000000000000001

所以 不完全是 10,而是偏离了最低有效位。

我在使用 float 时注意到了类似的差异。

10.f/3.f 打印为 %a:

0x1.aaaaaap+1

归一化:

0x3.555554

二进制:

11.0101010101010101010101

然后我们添加:

     11.0101010101010101010101
+    11.0101010101010101010101
------------------------------
    110.1010101010101010101010
+    11.0101010101010101010101
------------------------------
   1001.1111111111111111111111

同样,减去最低有效位。

至于实际结果如何四舍五入,我也不好说。

您在 C 中看到的内容与您在 SciLab 中看到的内容之间存在差异的原因是您使用的是 精度浮点值(float ) 在 C 中,而 SciLab 似乎默认使用 double-精度值 (double)。

您可以看到差异 here(只需从您的数字中删除 f 后缀,然后使用 double 而不是 float)。