【问题标题】:Floating-point directed roundings and optimization浮点定向舍入和优化
【发布时间】: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:一个更清楚的例子:

Correct rounding (-O0)

Failed rounding (-03)

很容易看出确切的结果:

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


【解决方案1】:

根据问题作者的评论,他们使用的编译器不支持 #pragma STDC FENV_ACCESS ON 并打印出这样的警告。

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

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

  • 编译器在编译时执行一些算术运算,忽略 fesetround 调用。
  • 在优化期间,编译器重新排序操作,可能执行算术运算和fesetround 调用的顺序与源代码显示的顺序不同。 fesetround 调用甚至可以被完全删除。

C 中可能没有解决此问题。如果编译器不支持访问浮点环境,则可能无法强制它生成必要的代码。声明某些对象volatile 可能会强制某些操作在执行时以所需的顺序执行,但编译器仍可能对这些操作重新排序fesetround,具体取决于其中内置了有关fesetround 的信息。

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

【讨论】:

    猜你喜欢
    • 2012-02-05
    • 1970-01-01
    • 2012-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多