Существует следующий код, в котором одно и то же выражение вычисляется в разных режимах округления:
#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
Почему нет режимы округления меняются?Как это исправить?
(если fesetround
вызывается на каждой итерации цикла for
(внутри функции foo
), то результаты верны с любыми флагами компиляции.)
UPD: Я думаю, проблема в том, что компилятор вычисляет значение fesetround
в типе компиляции, как указал @haneefmubarak в https://stackoverflow.com/a/26319847/2810512. Вопрос в том, как его предотвратить.(только для одной команды, 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 : более понятный пример:
Правильное округление (-O0)
Ошибкаокругление (-03)
Легко видеть, что точный результат:
2/3 + 2/5 + 4/7 + 4/11 = 2.0017316017316017316...