为什么 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
)。
看看这段代码。 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
)。