当我尝试在Intel I7 / I5上添加以下两个浮点数时,我发现了一个舍入错误:
2.500244140625E + 00 + 4503599627370496.00 < => 0x1.4008p + 1 + 0x1.0p + 52
由double
汇编指令(当我使用32位编译器编译时)使用两个faddl
精度常量进行的添加。
我获得的结果是:
4.50359962737049 8 E + 15 = 0x1.000000000000 2 p + 52
而不是:
4.50359962737049 9 E + 15 = 0x1.000000000000 3 p + 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.0000000000002 8 01p + 52 (完全)
0x10000000000002.801p + 0 = 0x1.000000000000 3 p + 52 (四舍五入后)
我在调试模式下仔细检查并验证我的FPU在"四舍五入到最近的模式"。
更令人奇怪的是,当我用64位编译器编译代码,然后使用addsd
指令时,没有舍入错误< /强>
是否有人可以参考或解释有关“双重”的精确差异?添加在同一个FPU上但使用不同的指令集?
答案 0 :(得分:2)
FPU寄存器为80位宽,只要加载fld
的单精度或双精度数,并且变量默认情况下将其转换为double extended precision 1 。
因此fadd
通常适用于80位数字。
SSE寄存器与格式无关,SSE扩展不支持双倍扩展精度。
例如,addpd
使用双精度数。
默认的舍入模式是舍入到最近(偶数),这意味着通常的舍入到最近的但是在平局的情况下朝向偶数末端(例如4.5 =&gt; ; 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
比1 |00 0
更接近0 |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 保护位是在其中一个数字被移位以使指数匹配后保留的额外精度位。比特的OR比最小的保护不那么重要。请参阅&#34;关于Rounding&#34; this page和Goldberg部分用于格式化方法。
答案 1 :(得分:-5)
首先,您要查看基数为10的数字。你想谈谈浮点和舍入,这些需要成为基础2讨论。
第二单和双有不同长度的尾数,所以很明显对于相同的数字,你的圆的位置在十进制1.2345678变化我们可以围绕它1.23或者可以绕它1.2346取决于我们允许一轮向上舍入的数字,采用综合规则。
由于您在这里处于基础10,因此您可能正在混合可能的编译时转换,运行时操作和运行时转换
我拿
float x=1.234567;
x=x*2.34;
printf("%f\n",x);
有编译时转换,first和formost ascii加倍然后double浮动到语言完全准确(没有把F&#39; s放在常量的末尾)。然后运行时间相乘,然后运行时转换为ascii,运行时C库可能与编译时不一样,它们是否遵循相同的舍入设置等等,很容易找到你简单声明的数字x = 1.234 ...然后下一行代码就是printf而printf不是你给它的东西,除了运行时浮点数到int之外没有浮点数学。
所以,在你提出这个问题之前,我们需要查看你的数字的二进制版本,你的问题几乎不会自动脱离的答案没有进一步的帮助,但是如果你仍然需要帮助那么发布它我们可以看看它。基于十进制的讨论会增加编译器和库问题,并且在出现问题时更难以隔离问题。