Направленные округления и оптимизация с плавающей точкой - PullRequest
0 голосов
/ 23 октября 2018

Существует следующий код, в котором одно и то же выражение вычисляется в разных режимах округления:

#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...

1 Ответ

0 голосов
/ 24 октября 2018

Согласно комментарию автора вопроса, используемый им компилятор не поддерживает #pragma STDC FENV_ACCESS ON и печатает предупреждение, говорящее об этом.

Код, скорее всего, «работает» в неоптимизированной версии, поскольку fesetround делаетизмените режим округления в аппаратном обеспечении, и компилятор выдает простой код, выполняя операции в номинальном порядке, представленном исходным кодом.

Причины, по которым оптимизированный код не работает, могут включать:

  • Компилятор выполняет некоторую арифметику во время компиляции, игнорируя вызовы fesetround.
  • Во время оптимизации компилятор переупорядочивает операции, возможно, выполняя арифметические операции и вызовы fesetround в другом порядке, чем показано в исходном коде.Вызовы fesetround могут быть даже полностью удалены.

Возможно, в C. этого не может быть исправлено. Если компилятор не поддерживает доступ к среде с плавающей запятой, может быть невозможно принудительно вызватьэто сгенерировать нужный код.Объявление некоторых объектов volatile может заставить некоторые операции выполняться во время выполнения и в желаемом порядке, но компилятор может все же переупорядочить fesetround относительно этих операций в зависимости от того, какая информация о fesetround встроена в него.

Может понадобиться использовать язык ассемблера для выполнения арифметики с плавающей запятой с желаемыми режимами округления.

...