【发布时间】:2018-10-23 13:48:43
【问题描述】:
有以下代码,在不同的舍入模式下对相同的表达式求值:
#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;
}
使用g++编译时,带有-O0选项,输出如下:
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
为什么舍入模式不改变?如何解决?
(如果在for 循环的每次迭代(在foo 函数内)调用fesetround,则任何编译标志的结果都是正确的。)
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:一个更清楚的例子:
很容易看出确切的结果:
2/3 + 2/5 + 4/7 + 4/11 = 2.0017316017316017316...
【问题讨论】:
-
我无法重现这种行为。
-
如果您将
foo(a,b)存储在变量c中并在您的所有printf语句中使用该变量,您会遇到同样的问题吗?。 -
你使用的GCC版本是否支持
#pragma STDC FENV_ACCESS ON? -
@EricPostpischil 有一个警告(来自 MinGW 的 g++)
pragma STDC FENV_ACCESS ON is not supported, ignoring pragma [-Wunknown-pragmas]但它适用于O0标志。 -
@kvantour 是的,问题出现了。
标签: c++ floating-point rounding ieee-754