以下代码在不同的舍入模式下对同一表达式进行求值:
#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...
最佳答案
根据问题作者的评论,他们使用的编译器不支持#pragma STDC FENV_ACCESS ON
并显示警告。
该代码可能会在未优化的版本中“起作用”,因为fesetround
确实会更改硬件中的舍入模式,并且编译器会以源代码所表示的名义顺序发出直接执行操作的代码。
优化的代码不起作用的原因可能包括:
编译器在编译时会执行一些算术运算,而忽略fesetround
调用。
在优化期间,编译器会对操作进行重新排序,可能以与源代码所示顺序不同的顺序执行算术运算和fesetround
调用。 fesetround
调用甚至可以完全删除。
C语言中可能没有针对此问题的修复程序。如果编译器不支持访问浮点环境,则可能没有办法强制其生成必要的代码。声明某些对象volatile
可能会强制某些操作在执行时按所需顺序执行,但是编译器可能仍根据这些操作对fesetround
进行重新排序,具体取决于内置了哪些有关fesetround
的信息。
可能需要使用汇编语言以所需的舍入模式执行浮点运算。
关于c++ - 浮点定向舍入和优化,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/52950747/