我正在玩一个在qemu中模拟的PPC64虚拟机,试图模拟POWER8 CPU。
这里,long double
类型不同于x86中用于长双的80位浮点,从我所看到的情况来看,它也不符合IEEE754的float128,因为根据C宏LDBL_MANT_DIG
,它有一个106位的尾数(而IEEE754为其float128指定的尾数为112位)。
维基百科说,IEEE754浮点128的机器ε应该在1.93e-34左右,这比80位x86浮点(1.08e-19)要好得多
然而,当我试图在这个虚拟机中获得机器epsilon时,我得到了一个相当令人惊讶的答案:
#include <iostream>
int main()
{
long double eps = 1.0l;
while (1.0l + 0.5l * eps != 1.0l)
eps = 0.5l * eps;
std::cout << eps << std::endl;
return 0;
}
它输出以下内容:
4.94066e-324
从LDBL_EPSILON
和std::numeric_limits<long double>::epsilon()
得到了相同的结果。
这将使它比预期的精确大约10倍,这一逻辑告诉我应该是不可能的。由于尾数正好是2x53(IEEE754的float64的尾数),我认为它可能使用了双双结构,维基百科也说它在小数字周围的精度应该低于IEEE754 float128。
这里发生了什么?
首先,让我假设您的操作系统是Linux。到目前为止,64位PowerPC上的所有编译器默认情况下都使用long double
的"双双"类型,其格式不符合IEEE。
该格式实际上是两个double
的组合,因此请将其理解为struct { double high; double low; }
。高部分与正常的double
没有什么不同,而低部分提供扩展的尾数。整体的指数与double
相同,这意味着它的最大可表示数不比double
大几个数量级(由于双双尾数更长,所以它们仍然不同)。
当前双双浮点数的操作不支持本机PowerPC指令。长double的a+b
最终将被转换为对函数__gcc_qadd
的调用。(LLVM的编译器rt和GCC的libgcc都有它们的实现,请参阅add函数的源代码)
由于POWER ISA 3.0(Power9),因此支持"binary128"(符合IEEE的128位浮点型)的本机指令支持。您可以使用-mcpu=power9 -mfloat128
来启用该功能,并使用__float128
来表示它,或者添加-mabi=ieeelongdouble
以使编译器将长双精度视为binary128,而不是双精度(以及C库声明)。
Binary128不是两个双精度的组合,而是用矢量寄存器存储/传递,并且具有比双精度好得多的精度,尾数为112位,指数为15位。GCC/Clang实际上通过在编译器rt/libgcc中使用支持函数,为带有VSX(Power7或更高版本)的目标支持binary128。(例如,a+b
为Power9及更高版本生成xsaddqp
指令,为Power7和Power8生成__addkf3
指令)
如果您的工具链相对较新(例如,高级工具链14或更新版本),请尝试使用C代码启用-mabi=ieeelongdouble
。在C++库支持完成后,GCC和Clang计划将来在64位little-endian上将默认的长双类型切换为binary128。