浮点定向舍入和优化

时间:2018-10-23 13:48:44

标签: c++ floating-point rounding ieee-754

下面的代码在不同的舍入模式下对同一表达式进行求值:

#include <iostream>
#include <fenv.h>
#pragma STDC FENV_ACCESS ON
#define SIZE 8

double foo(double * a, double * b){
    double sum = 0.0;
    for(unsigned int i = 0; i < SIZE; i++) {
        sum+= b[i] / a[i];
    }
    return sum;
}

int main() {
    double a[]={127, 131, 137, 139, 149, 151, 157, 163};
    double b[SIZE];

    for(unsigned int i = 0; i < SIZE; i++){
        b[i] = i+1;
    }

    printf("to nearest:   %.18f \n", foo(a, b));

    fesetround(FE_TOWARDZERO);
    printf("toward zero:  %.18f \n", foo(a, b));

    fesetround(FE_UPWARD);
    printf("to +infinity: %.18f \n", foo(a, b));

    fesetround(FE_DOWNWARD);
    printf("to -infinity: %.18f \n", foo(a, b));

    return 0;
}

使用带有-O0选项的g ++进行编译时,输出如下:

to nearest:   0.240773868136782450
toward zero:  0.240773868136782420
to +infinity: 0.240773868136782560
to -infinity: 0.240773868136782420

但是使用-O3选项进行编译时,我们有:

to nearest:   0.240773868136782480
toward zero:  0.240773868136782480
to +infinity: 0.240773868136782480
to -infinity: 0.240773868136782480

编译器: g++ (MinGW.org GCC-6.3.0-1) 6.3.0

为什么舍入模式不变?如何解决?

(如果fesetround循环的每次迭代(在for函数内部)都调用了foo,则结果与任何编译标志都是正确的。)

UPD::我认为问题在于,编译器会根据{haneefmubarak在https://stackoverflow.com/a/26319847/2810512中指出的那样,在编译类型时计算fesetround的值。问题是如何预防。 (仅适用于一个命令fesetround,不适用于整个功能)。

我编写了用于使用__attribute__ ((noinline))舍入例程的包装器,并在main函数中调用了它们:

void __attribute__ ((noinline)) rounddown(){
    fesetround(FE_DOWNWARD);
}

void __attribute__ ((noinline)) roundup(){
    fesetround(FE_UPWARD);
}

int main() {
    ...

    roundup();
    printf("to +infinity: %.18f \n", foo(a, b));

    rounddown();
    printf("to -infinity: %.18f \n", foo(a, b));
    ...
}

但是它不起作用。有什么想法吗?

UPD2 :一个更清晰的示例:

Correct rounding (-O0)

Failed rounding (-03)

很容易看到确切的结果:

2/3 + 2/5 + 4/7 + 4/11 = 2.0017316017316017316...

1 个答案:

答案 0 :(得分:4)

根据问题作者的评论,他们使用的编译器不支持fesetround并显示警告。

代码可能会在未优化的版本中“起作用”,因为fesetround确实改变了硬件中的舍入模式,并且编译器以源代码表示的名义顺序发出了直接执行操作的代码。

原因是优化后的代码不起作用,可能包括:

  • 编译器在编译时执行一些算术运算,而忽略了fesetround调用。
  • 在优化过程中,编译器对操作进行重新排序,可能以与源代码所示顺序不同的顺序执行算术操作和fesetround调用。 volatile呼叫甚至可能被完全删除。

在C中可能没有针对此的修复程序。如果编译器不支持访问浮点环境,则可能没有办法强制其生成必要的代码。声明一些对象fesetround可能会强制某些操作在执行时按所需顺序执行,但是编译器可能仍会根据这些操作对fesetround重新排序,具体取决于有关{{1}的信息}内置于其中。

可能需要使用汇编语言以所需的舍入模式执行浮点运算。

相关问题