用于提高中间浮点计算精度的编译器标志

Posted

技术标签:

【中文标题】用于提高中间浮点计算精度的编译器标志【英文标题】:Compiler flags for enhancing the precision of intermediate floating-point calculation 【发布时间】:2016-08-07 15:46:22 【问题描述】:

gcc/clang 中是否有指定中间浮点计算精度的标志?

假设我有一个 C 代码

double x = 3.1415926;
double y = 1.414;
double z = x * y;

是否有编译器标志允许以用户机器的最高可能精度计算“x*y”,例如 long-double(64 位尾数),然后截断回 double(53 位尾数,声明变量类型的精度)?

仅供参考,我在 64 位机器上使用 Ubuntu 14.04。

【问题讨论】:

【参考方案1】:

GCC

[编辑观察到的 gcc 4.8.4 行为,其中默认行为与文档相反]

您需要使用 x87 FPU 中的 80 位寄存器。使用 -mfpmath=387,您可以覆盖 SSE 寄存器 XMM0-XMM7 的默认使用。这个默认值实际上为您提供了每一步都使用 64 位寄存器的 IEEE 行为。

见:https://gcc.gnu.org/wiki/x87note

因此,默认情况下 x87 算术不是真正的 64/32 位 IEEE,而是得到 x87 单元的扩展精度。然而,任何时候一个值是 从寄存器移动到 IEEE 64 或 32 位存储位置, 这个 80 位的值必须向下舍入到适当的 位。

但是,如果您的操作非常复杂,则可能会发生寄存器溢出; FP 寄存器堆栈的深度仅为 8。因此,当溢出复制到字大小的 RAM 位置时,您将得到四舍五入。在这种情况下,您要么需要自己声明 long double 并在最后手动舍入,要么检查汇编程序输出是否有显式溢出。

更多关于寄存器的信息在这里: https://software.intel.com/en-us/articles/introduction-to-x64-assembly

特别是,XMM0...7 寄存器虽然为 128 位宽,但只能容纳两个同时进行的 64 位 FP 操作。因此,您希望看到带有 FLD(加载)、FMUL(乘法)和 FSTP(存储和弹出)指令的堆栈操作 FPR 寄存器。

所以我编译了这段代码:

double mult(double x, double y) 
    return x * y;

与:

gcc -mfpmath=387 -Ofast -o precision.s -S precision.c

得到:

mult:
  .LFB24:
    .cfi_startproc
    movsd   %xmm1, -8(%rsp)
    fldl    -8(%rsp)
    movsd   %xmm0, -8(%rsp)
    fldl    -8(%rsp)
    fmulp   %st, %st(1)
    fstpl   -8(%rsp)
    movsd   -8(%rsp), %xmm0
    ret
    .cfi_endproc

现在一切都说得通了。浮点值通过寄存器 XMM0 和 XMM1 传递(尽管它们必须在内存中进行一次奇怪的往返,然后才能放入 FPR 堆栈),并根据上述 Intel 参考在 XMM0 中返回结果。不知道为什么没有直接来自 XMM0/1 的简单 FLD 指令,但显然指令集没有这样做。

如果您与-mfpmath=sse 相比,在后一种情况下要做的事情要少得多,因为操作数已准备好并在 XMM0/1 寄存器中等待,并且就像单个 MULSD 指令一样简单。

【讨论】:

非常感谢。顺便说一句,您答案中的“溢出”是否意味着“溢出”? 不,这是一个编译器优化术语,意思是“实时”(计算所需)值太多,因此它们不能全部放入寄存器,因此必须将一个溢出到内存中,以便腾出空间另一个。 谢谢。我检查了上面介绍的小程序生成的二进制代码。生成的一段代码是:addsd -8(%rbp), %xmm0, movsd %xmm0, -16(%rbp), movq -16(%rbp), %rax"。我们怎么知道位数在寄存器 xmm0 中? 答案已相应编辑。我在上面的 sn-p 中既没有看到 FMUL 也没有看到 MULSD,所以我不知道你调用了哪种类型的算术。 “在这种情况下,您要么需要自己声明 long double 并在最后手动舍入,要么检查汇编器输出是否有显式溢出。”:使用long double 是一个很好的解决方案,它不会需要-mfpmath=387。然而,比检查汇编程序是否溢出更简单的是,在编译 C 时,使用-std=c99-std=c11,这使得 GCC 坚持 C99 中FLT_EVAL_METHOD 的描述,并使用long double 精度进行所有中间计算。然后,如果发生溢出,则溢出一个 80 位的long double,并且溢出在功能上是透明的。

以上是关于用于提高中间浮点计算精度的编译器标志的主要内容,如果未能解决你的问题,请参考以下文章

请问浮点型数据在计算机是怎么存储的

如何保持浮点/双精度算术确定性?

如何将模数用于浮点/双精度?

float精度问题

js 浮点小数计算精度问题 parseFloat 精度问题

Scilab中浮点计算的精度是多少?