Floating point 确定C/C+中的浮点运算是否发生舍入+;

Floating point 确定C/C+中的浮点运算是否发生舍入+;,floating-point,rounding,ieee-754,Floating Point,Rounding,Ieee 754,我正试图找到一种有效的方法来确定IEEE-754操作的舍入时间。不幸的是,我不能简单地检查硬件标志。它必须在几个不同的平台上运行 我想到的方法之一是在不同的取整模式下执行操作,以比较结果 添加示例: double result = operand1 + operand2; // save rounding mode int savedMode = fegetround(); fesetround(FE_UPWARD); double upResult =

我正试图找到一种有效的方法来确定IEEE-754操作的舍入时间。不幸的是,我不能简单地检查硬件标志。它必须在几个不同的平台上运行

我想到的方法之一是在不同的取整模式下执行操作,以比较结果

添加示例:

    double result = operand1 + operand2;
    // save rounding mode
    int savedMode = fegetround();
    fesetround(FE_UPWARD);
    double upResult = operand1 + operand2;
    fesetround(FE_DOWNWARD);
    double downResult = operand1 + operand2;
    // restore rounding mode
    fesetround(savedMode);
    return (result != upResult) || (result != downResult);

但这显然是低效的,因为它必须执行3次操作。

您的示例不一定给出正确的优化结果 级别
-O1
或更高。见此: 编译器只生成一个加法
vaddsd

优化 级别
-O0
程序集看起来正常,但这会导致代码效率低下。 此外,调用
fegetround
fesetround
相对昂贵, 与一些浮点运算的成本相比

下面的(自我解释)代码可能是一个有趣的选择。 它使用了著名的算法2Sum和2ProdFMA。在没有硬件fma或fma仿真的系统上,可以使用2Prod算法代替2ProdFMA, 例如,请参见精确浮点乘积和幂运算, 作者:斯特夫·格拉利塔

/*
gcc-m64-Wall-O3-march=haswell round_ex.c-lm
或在不支持硬件fma的系统上进行fma仿真,例如:
gcc-m64-Wall-O3-march=nehalem圆形_ex.c-lm
*/
#包括
#包括
#包括
int add不精确(双操作数1,双操作数2){
双a=操作数1;
双b=操作数2;
双s,t,a_1,b_1,d_a,d_b;
/*算法2Sum计算s和t,使a+b=s+t,精确*/
/*这里t是浮点加法的误差s=a+b*/
/*例如,请参见2Sum和Fast2Sum算法的健壮性*/
/*波尔多、格拉利特和穆勒*/
s=a+b;
a_1=s-b;
b_1=s-a_1;
d_a=a-a_1;
d_b=b-b_1;
t=d_a+d_b;
返回(t!=0.0);
}
int sub_不精确(双操作数1,双操作数2){
返回加法不精确(操作数1,-操作数2);
}
int mul_不精确(双操作数1,双操作数2){
双a=操作数1;
双b=操作数2;
双s,t;
/*算法2ProdFMA计算s和t,使a*b=s+t,精确*/
/*这里t是浮点乘法s=a*b的误差*/
/*例如,请参见精确浮点乘积和幂运算*/
/*颗粒状*/
s=a*b;
t=fma(a,b,-s);
如果(s!=0)返回(t!=0.0);/*没有a*b的下溢*/
else返回(a!=0.0)&(b!=0.0);/*下溢:如果s=0,则不精确,但是(a!=0.0)&&(b!=0.0)*/
}
int div_不精确(双操作数1,双操作数2){
双a=操作数1;
双b=操作数2;
双s,t;
s=a/b;
t=fma(s,b,-a);/*fma(x,y,z)以无限中间精度计算x*y+z*/
返回(t!=0.0);
}
int main(){
printf(“添加不精确(10.0,1.0)=%i\n”,添加不精确(10.0,1.0));
printf(“sub_不精确(10.0,1.0)=%i\n”,sub_不精确(10.0,1.0));
printf(“mul_不精确(2.5,2.5)=%i\n”,mul_不精确(2.5,2.5));
printf(“div_不精确(10,2.5)=%i\n”,div_不精确(10,2.5));
printf(“添加不精确(10.0,0.1)=%i\n”,添加不精确(10.0,0.1));
printf(“sub_不精确(10.0,0.1)=%i\n”,sub_不精确(10.0,0.1));
printf(“mul_不精确(2.6,2.6)=%i\n”,mul_不精确(2.6,2.6));
printf(“div_不精确(10,2.6)=%i\n”,div_不精确(10,2.6));
printf(“\n0x1.0p-300=%20e,0x1.0p-600=%20e\n”,0x1.0p-300,0x1.0p-600);
printf(“mul_不精确(0x1.0p-300,0x1.0p-300)=%i\n”,mul_不精确(0x1.0p-300,0x1.0p-300));
printf(“mul_不精确(0x1.0p-600,0x1.0p-600)=%i\n”,mul_不精确(0x1.0p-600,0x1.0p-600));
}
输出为:

$ ./a.out
add_is_not_exact(10.0, 1.0) = 0
sub_is_not_exact(10.0, 1.0) = 0
mul_is_not_exact( 2.5, 2.5) = 0
div_is_not_exact(  10, 2.5) = 0
add_is_not_exact(10.0, 0.1) = 1
sub_is_not_exact(10.0, 0.1) = 1
mul_is_not_exact( 2.6, 2.6) = 1
div_is_not_exact(  10, 2.6) = 1

0x1.0p-300 =         4.909093e-91, 0x1.0p-600 =        2.409920e-181 
mul_is_not_exact( 0x1.0p-300, 0x1.0p-300) = 0
mul_is_not_exact( 0x1.0p-600, 0x1.0p-600) = 1


如评论中所述,也可以直接阅读 控制和状态寄存器:

#包括
#布拉格STDC FENV_通道
int add是不精确的(双a,双b)
{    
F例外情况;
FeClearException(FE_ALL_除外);
双c=a+b;
int tst=异常(FE_不精确);
返回(tst!=0);
}
但是,请注意,这可能不适用于编译器优化级别-O1或更高级别。 在这种情况下,
addsd
double add指令有时会被完全优化, 导致错误的结果。 例如,使用gcc 8.2
gcc-m64-O1-march=nehalem

add_is_not_exact_v2:
        sub     rsp, 8
        mov     edi, 61
        call    feclearexcept
        mov     edi, 32
        call    fetestexcept
        test    eax, eax
        setne   al
        movzx   eax, al
        add     rsp, 8
        ret
具有优化级别
-O0
,具有2个函数调用,并且具有相对的
扩展指令修改控件和状态寄存器,这不一定是最有效的解决方案

当您说您不能检查硬件标志时,这是否也意味着您不能使用标准C
fegetexceptflag
?如果是这样的话,如果有一个好的解决方案,我会感到惊讶。通常,当标准C例程没有用处时(由于缺乏编译器支持),解决方案是编写内联汇编并为每个平台自定义它。@EricPostChil通过检查硬件标志,我的意思是显式地执行操作并读取内联程序集中的标志。使用
fegetexceptflag
可能没问题,但如果我使用它,检索到的标志是否保证与刚才执行的浮点操作相对应?浮点标志是累积的,这意味着操作会设置它们,并且它们会一直保持设置状态,直到重置为止。因此,如果调用
fegetexceptflag
并发现不精确的标志已被提升,则它可能来自最近的操作或任何操作