二进制64浮点加法舍入模式错误和行为差异32/64位

时间:2017-05-10 16:56:47

标签: c assembly floating-point floating-accuracy fpu

当我尝试在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上但使用不同的指令集?

2 个答案:

答案 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 11 |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 pageGoldberg部分用于格式化方法。

答案 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之外没有浮点数学。

所以,在你提出这个问题之前,我们需要查看你的数字的二进制版本,你的问题几乎不会自动脱离的答案没有进一步的帮助,但是如果你仍然需要帮助那么发布它我们可以看看它。基于十进制的讨论会增加编译器和库问题,并且在出现问题时更难以隔离问题。