用于提高中间浮点计算精度的编译器标志
Compiler flags for enhancing the precision of intermediate floating-point calculation
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。
海湾合作委员会
[编辑观察到的 gcc 4.8.4 行为,其中默认行为与文档相反]
您需要使用 x87 FPU 中的 80 位寄存器。使用 with -mfpmath=387
可以覆盖 SSE 寄存器 XMM0-XMM7 的默认使用。此默认值实际上为您提供了 IEEE 行为,其中每一步都使用 64 位寄存器。
参见:https://gcc.gnu.org/wiki/x87note
Thus, by default x87 arithmetic is not true 64/32 bit IEEE, but gets
extended precision from the x87 unit. However, anytime a value is
moved from the registers to an IEEE 64 or 32 bit storage location,
this 80 bit value must be rounded down to the appropriate number of
bits.
但是,如果您的操作非常复杂,则可能会发生寄存器溢出; 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 堆栈之前它们必须通过内存进行奇怪的往返),并且根据上述英特尔参考,结果在 XMM0 中返回。不知道为什么没有直接来自 XMM0/1 的简单 FLD 指令,但显然指令集不这样做。
如果与 -mfpmath=sse
相比,在后一种情况下需要做的事情要少得多,因为操作数已经准备好并在 XMM0/1 寄存器中等待,它就像一个简单的MULSD 指令。
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。
海湾合作委员会
[编辑观察到的 gcc 4.8.4 行为,其中默认行为与文档相反]
您需要使用 x87 FPU 中的 80 位寄存器。使用 with -mfpmath=387
可以覆盖 SSE 寄存器 XMM0-XMM7 的默认使用。此默认值实际上为您提供了 IEEE 行为,其中每一步都使用 64 位寄存器。
参见:https://gcc.gnu.org/wiki/x87note
Thus, by default x87 arithmetic is not true 64/32 bit IEEE, but gets extended precision from the x87 unit. However, anytime a value is moved from the registers to an IEEE 64 or 32 bit storage location, this 80 bit value must be rounded down to the appropriate number of bits.
但是,如果您的操作非常复杂,则可能会发生寄存器溢出; 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 堆栈之前它们必须通过内存进行奇怪的往返),并且根据上述英特尔参考,结果在 XMM0 中返回。不知道为什么没有直接来自 XMM0/1 的简单 FLD 指令,但显然指令集不这样做。
如果与 -mfpmath=sse
相比,在后一种情况下需要做的事情要少得多,因为操作数已经准备好并在 XMM0/1 寄存器中等待,它就像一个简单的MULSD 指令。