在一些Juniper MX路由器上,浮点处理不正确:如果在计算过程中向右移动超过8位(下溢),则粘性位将丢失。有解决办法吗?是否存在任何已知的影响?它修好了吗?这是IEEE可以接受的选项吗?其他系统中是否存在此问题?
数学细节示例(最好使用固定宽度字体和宽屏幕查看):
1
shifts: 12345678901
4095.05615204245304994401521980762481689453125000000000 = 0x1.ffe1cbff5e3e1p+11 = 0x40affe1cbff5e3e1 = 111111111111.00001110010111111111101011110001111100001
+ 1.0000137123424794882708965815254487097263336181640625 = 0x1.0000e60e10001p+0 = 0x3ff0000170168000 = 1.0000000000000000111001100000111000010000000000000001
^
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1000000000000.000011100110000011100001000000000000000010s
LGRS
0101
1 2 3 4 5
mantissa bit #: 1234567890123 4567890123456789012345678901234567890123
4096.0561657547959839575923979282379150390625 = 0x1.0000e60e10001p+12 = 0x40b0000e60e10001 = 1000000000000.0000111001100000111000010000000000000001 (on "all" systems/correct)
4096.056165754795074462890625 = 0x1.0000e60e10000p+12 = 0x40b0000e60e10000 = 1000000000000.0000111001100000111000010000000000000000 (on Juniper router)
^ ^ ^ ^
互联网消息来源告诉我,许多MX系列路由器使用Intel x86 CPU。当x87配置为在扩展精度模式下操作时,观察到的行为与使用x87 FPU进行浮点计算(与SSE或AVX相反)完全一致。
x87 FPU将所有操作数存储在80位寄存器中,其中每个寄存器使用64个有效位(尾数)保存一个浮点操作数,有效位的整数位是显式的。FPU控制字的位8和9表示精度控制字段,该字段指示FPU将在哪个位位置对结果进行舍入。设置为2相当于双倍精度,而设置为3表示舍入到扩展精度。
大多数类Unix的32位操作系统将x87舍入控制设置为3,而Windows将其设置为2。我不知道现代的Junos是32位操作系统还是64位操作系统。出于向后兼容性的原因,它可能会保留x87和FPU精度控制设置3的使用。
x87精度控制设置为3(扩展精度)时,双舍入存在问题。浮点运算的结果首先四舍五入到扩展精度,并存储在内部FPU寄存器中。稍后,当该结果存储到与double
变量相对应的存储位置时,数据从寄存器中取出并再次四舍五入。
我使用英特尔编译器在Windows64上对问题的具体场景进行了编程,以便轻松访问x87汇编语言指令。程序以三种不同的格式(十进制浮点、十六进制浮点和二进制)转储两个源操作数a
和b
以及和r
,还转储这些操作数的内部80位表示(带有t
前缀)。
通过将USE_X87_EXTENDED_PRECISION
定义为0
或1
,可以在计算之前将FPU的精度控制设置为双精度或扩展精度,并且相关FPU控制字的值显示为compute cw
。当USE_X87_EXTENDED_PRECISION
设置为0
时,程序的输出为:
original cw=027f
compute cw=027f
a=4.0950561520424530e+003 0x1.ffe1cbff5e3e1p+11 40affe1cbff5e3e1 ta=400afff0e5ffaf1f0800
b=1.0000137123424795e+000 0x1.0000e60e10001p+0 3ff0000e60e10001 tb=3fff8000730708000800
r=4.0960561657547960e+003 0x1.0000e60e10001p+12 40b0000e60e10001 tr=400b8000730708000800
然而,当USE_X87_EXTENDED_PRECISION
是1
时,结果是:
original cw=027f
compute cw=037f
a=4.0950561520424530e+003 0x1.ffe1cbff5e3e1p+11 40affe1cbff5e3e1 ta=400afff0e5ffaf1f0800
b=1.0000137123424795e+000 0x1.0000e60e10001p+0 3ff0000e60e10001 tb=3fff8000730708000800
r=4.0960561657547951e+003 0x1.0000e60e10000p+12 40b0000e60e10000 tr=400b8000730708000400
在第二次取整期间,从tr
到r
,取整位为1,但粘性位为0,因为经过取整位的所有尾随有效位都为零,所以"0";甚至";默认舍入模式的一部分";四舍五入到最接近或甚至"0";
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#define USE_X87_EXTENDED_PRECISION (1)
typedef struct tbyte {
uint64_t l;
uint16_t h;
} tbyte;
uint64_t double_as_uint64 (double a)
{
uint64_t r; memcpy (&r, &a, sizeof r); return r;
}
int main (void)
{
double a = 0x1.ffe1cbff5e3e1p+11;
double b = 0x1.0000e60e10001p+0;
double r;
uint16_t cw_orig, cw_comp, cw_temp;
tbyte ta, tb, tr;
__asm fstcw word ptr [cw_orig];
#if USE_X87_EXTENDED_PRECISION
cw_temp = cw_orig | (3 << 8);
__asm fldcw word ptr [cw_temp];
#endif // USE_X87_EXTENDED_PRECISION
__asm fstcw word ptr [cw_comp];
__asm fld qword ptr [a];
__asm fld qword ptr [b];
__asm fld st(1);
__asm fadd st, st(1);
__asm fst qword ptr [r];
__asm fstp tbyte ptr [tr];
__asm fstp tbyte ptr [tb];
__asm fstp tbyte ptr [ta];
__asm fldcw word ptr [cw_orig];
printf ("original cw=%04xn", cw_orig);
printf ("compute cw=%04xn", cw_comp);
printf ("a=%23.16e %21.13a %016llx ta=%04x%016llxn", a, a, double_as_uint64 (a), ta.h, ta.l);
printf ("b=%23.16e %21.13a %016llx tb=%04x%016llxn", b, b, double_as_uint64 (b), tb.h, tb.l);
printf ("r=%23.16e %21.13a %016llx tr=%04x%016llxn", r, r, double_as_uint64 (r), tr.h, tr.l);
return EXIT_SUCCESS;
}