当我尝试在英特尔酷睿 I7/I5 上添加以下两个浮点数时,我注意到一个舍入错误:
2.500244140625E+00 + 4503599627370496.00 <=>0x1.4008p+1 + 0x1.0p+52
添加,由faddl
汇编指令(当我使用 32 位编译器编译时)使用两个double
精度常数进行。
我得到的结果是:
4.503599627370498E+15 = 0x1.0000000000002p+52
而不是:
4.503599627370499E+15 = 0x1.0000000000003p+52
(正如我所期望的那样,并得到了 http://weitz.de/ieee/的证实。
示范:
0x1.0p+52 = 0x10000000000000.00p+0
0x1.4008p+1 = 0x2.801p+0
0x10000000000000.00p+0 + 0x2.801p+0 = 0x10000000000002.801p+0(确切)
0x10000000000002.801p+0 = 0x1.0000000000002801p+52(正好)
0x10000000000002.801p+0 = 0x1.0000000000003p+52(四舍五入后)
我在调试模式下仔细检查并验证我的 FPU 是否处于"舍入到最接近模式"。
更奇怪的是,当我用 64 位编译器编译我的代码,然后使用addsd
指令时,没有舍入错误。
有没有人可以给我参考或解释关于在同一 FPU 上使用不同指令集的"双重"加法的精度差异?
FPU 寄存器的宽度为 80 位,每当fld
加载单精度或双精度数字时,默认情况下会将其转换为双精度扩展精度1。
因此fadd
通常适用于 80 位数字。
SSE 寄存器与格式无关,SSE 扩展不支持双扩展精度。
例如,addpd
使用双精度数字。
),这意味着通常的舍入到最接近,但在平局的情况下朝向偶数端(例如 4.5 => 4)。
为了实现 IEEE 754 要求,以无限精度的数字执行算术运算,硬件需要两个保护位和一个粘性位2
双
我将一个双精度数字写为
<sign> <unbiased exponent in decimal> <implicit integer part> <52-bit mantissa> | <guard bits> <sticky bit>
两个数字
2.500244140625
4503599627370496
是
+ 1 1 0100000000 0010000000 0000000000 0000000000 0000000000 00
+ 52 1 0000000000 0000000000 0000000000 0000000000 0000000000 00
第一个是移位的
+ 52 0 0000000000 0000000000 0000000000 0000000000 0000000000 10 |10 1
+ 52 1 0000000000 0000000000 0000000000 0000000000 0000000000 00 |00 0
总和完成
+ 52 1 0000000000 0000000000 0000000000 0000000000 0000000000 10 |10 1
四舍五入到最接近(偶数)得到
+ 52 1 0000000000 0000000000 0000000000 0000000000 0000000000 11
因为0 |10 1
比0 |00 0
更接近1 |00 0
.
双扩展
这两个数字是
+ 1 1 0100000000 0010000000 0000000000 0000000000 0000000000 0000000000 000
+ 52 1 0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 000
第一个是转移
+ 52 0 0000000000 0000000000 0000000000 0000000000 0000000000 1010000000 000 | 10 0
+ 52 1 0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 000 | 00 0
总和完成
+ 52 1 0000000000 0000000000 0000000000 0000000000 0000000000 1010000000 000 | 10 0
四舍五入到最接近(偶数):
+ 52 1 0000000000 0000000000 0000000000 0000000000 0000000000 1010000000 000
由于0 | 10 0
被领带打破到最近的偶数。
当这个数字从双倍扩展精度转换为双倍精度(由于fstp QWORD []
)时,使用双倍扩展尾数的第52、53和54位作为保护和粘性位重复舍入。
+ 52 1 0000000000 0000000000 0000000000 0000000000 0000000000 1010000000 000
+ 52 1 0000000000 0000000000 0000000000 0000000000 0000000000 10|100
+ 52 1 0000000000 0000000000 0000000000 0000000000 0000000000 10
因为0|100
再次被领带打破到最近的偶数。
1请参阅英特尔手册 - 第 1 卷的第 8.5.1.2 章。
2保护位是在其中一个数字移动以使指数匹配后保留的额外精度位。粘性位它比最少保护的位不重要。请参阅本页的"四舍五入"部分和 Goldberg 了解格式方法。
感谢我的问题收到的所有评论,我了解发生了什么,并能够解决问题。
我将尝试在这里总结一下。
首先,确认了不正确的四舍五入。正如@MarkDickinson所提到的,这可能是由于"双重四舍五入",但我不知道是否可以确认。事实上,这也可能是由于其他现象造成的,例如Pascal Cuoq给出的出版物中描述的现象。
在对某些数字进行四舍五入的问题时,IA32 FPU 似乎并不完全符合 IEEE754 标准。
默认情况下,GCC(32 位版本)生成的代码使用 FPU 计算 Binary64 数字的加法。
但是,在我的电脑(英特尔酷睿i7)上,SSE单元也能够进行这些计算。默认情况下,GCC(64 位版本)使用此单位。
在GCC32命令行上使用以下两个选项解决了我的问题。
-msse2 -mfpmath=SSE.
(谢谢EOF)
首先,您正在查看以 10 为基数的数字。 你想谈论浮点和舍入,这需要是一个基数 2 的讨论。
第二个单尾数和双尾数的长度不同,所以很明显,对于相同的数字,您舍入的地方以十进制 1.2345678 变化,我们可以将其四舍五入 1.23 或可以四舍五入 1.2346,具体取决于我们允许的位数向上舍入一个向下舍入,采用舍入规则。
由于您在这里的某个时候是基数 10,因此您还混合了可能的编译时转换、运行时操作和运行时转换
我拿
float x=1.234567;
x=x*2.34;
printf("%fn",x);
有编译时转换,首先是ASCII加倍,然后加倍到浮点数以完全准确地对语言准确(没有将F放在常量的末尾)。 然后运行时乘以,然后运行时转换为 ASCII,运行时 C 库可能与编译时不同,它们是否遵循相同的舍入设置等。 很容易找到你简单地声明 x=1.234 的数字......然后下一行代码是 printf,printf 不是你输入它的东西,除了运行时浮点到 int 之外没有浮点数学。
因此,在您提出此问题之前,我们需要查看您的数字的二进制版本,您的问题的答案几乎应该自动从中消失,而无需进一步的帮助,但是如果您仍然需要帮助,请发布它,我们可以查看它。 基于十进制的讨论会增加编译器和库问题,并且在出现问题时更难隔离问题。