long double(特定于GCC)和__float128

Dam*_*mon 28 gcc long-double

我正在寻找的详细信息long double,并__float128在GCC/86(更多的是出于好奇不是因为的实际问题).

很少有人可能会需要这些(我只是,这是第一次真正需要的double),但我想你知道你的工具箱里有什么以及它的含义仍然值得(而且很有趣).

有鉴于此,请原谅我有些开放的问题:

  1. 有人可以解释这些类型的实现原理和预期用法,也可以相互比较吗?例如,它们是"尴尬实施",因为标准允许类型,有人可能会抱怨,如果它们只是相同的精度double,或者它们是否属于一流类型?
  2. 或者,有人有一个好的,可用的网络参考分享?谷歌搜索"long double" site:gcc.gnu.org/onlinedocs没有给我很多真正有用的东西.
  3. 假设常见的口头禅"如果你认为你需要加倍,你可能不理解浮点"不适用,即你真的需要更多的精度而不仅仅是float,并且不关心是8或16字节的内存是烧毁...是合理的期望,人们可以也只是跳long double__float128代替double无显著的性能影响?
  4. 当值在内存和寄存器之间移动时,英特尔CPU的"扩展精度"功能一直是令人讨厌的惊喜的源头.如果实际存储了96位,则该long double类型应该消除此问题.另一方面,我理解long double类型是互斥的-mfpmath=sse,因为SSE中没有"扩展精度"这样的东西.__float128另一方面,SSE数学应该可以完美地工作(尽管没有四分之一精度指令肯定不适用于1:1指令库).我对这些假设是对的吗?

(3.和4.可能可以通过花费在分析和反汇编上的一些工作来解决,但也许其他人之前有过相同的想法,并且已经完成了这项工作.)

背景(这是TL; DR部分):
我最初绊倒了long double,因为我一直在寻找起来DBL_MAX<float.h>,并incidentially LDBL_MAX是在下一行."哦,看,GCC实际上有128位双打,不是我需要它们,但是......很酷"是我的第一个念头.惊喜,惊喜:sizeof(long double)回报12 ......等等,你的意思是16?

毫不奇怪,C和C++标准没有给出类型的非常具体的定义.C99(6.2.5 10)表示数字doublelong doubleC++ 03状态(3.9.1 8)的一个子集,long double其精度至少与double(同样的,只是措辞不同)相同.基本上,标准把一切的实现,以同样的方式与long,intshort.

维基百科说,无论使用何种物理存储,GCC都在x86处理器上使用"80位扩展精度".

GCC文档在同一页面上指出,由于i386 ABI,该类型的大小为96位,但任何选项都启用了不超过80位的精度(嗯?什么?),还有Pentium和更新版本处理器希望它们被对齐为128位数.这是64位下的默认值,可以在32位下手动启用,从而产生32位零填充.

是时候进行测试了:

#include <stdio.h>
#include <cfloat>

int main()
{
#ifdef  USE_FLOAT128
    typedef __float128  long_double_t;
#else
    typedef long double long_double_t;
#endif

long_double_t ld;

int* i = (int*) &ld;
i[0] = i[1] = i[2] = i[3] = 0xdeadbeef;

for(ld = 0.0000000000000001; ld < LDBL_MAX; ld *= 1.0000001)
    printf("%08x-%08x-%08x-%08x\r", i[0], i[1], i[2], i[3]);

return 0;
}
Run Code Online (Sandbox Code Playgroud)

使用时的输出long double看起来有点像这样,标记的数字是不变的,所有其他的数据最终会随着数字越来越大而变化:

5636666b-c03ef3e0-00223fd8-deadbeef
                  ^^       ^^^^^^^^
Run Code Online (Sandbox Code Playgroud)

这表明它不是 80位数.80位数字有18个十六进制数字.我看到22个十六进制数字在变化,看起来更像96位数字(24个十六进制数字).它也不是128位数字,因为0xdeadbeef没有触及,这与sizeof返回12 一致.

__int128看起来它的输出实际上只是一个128位的数字.所有位最终都会翻转.

编译具有-m128bit-long-double对准long double到128位与32位的零填充,由文档所指示的.它也没有使用__int128,但实际上似乎与128位对齐,填充值0x7ffdd000(?!).

而且,LDBL_MAX似乎和+inf两者兼而有之.加上或减去像数或到/从在相同的位模式的结果. 到现在为止,这是我的信念,即常数持有最大可表示数是不是(显然这是不是这样的?).我也不太确定一个80位的数字如何可以理解为128位的价值...也许我在一天结束时太累了并且做错了什么.long double__float1281.0E1001.0E2000LDBL_MAX
foo_MAX +inf+inf

Cal*_*dan 22

广告1.

这些类型设计用于处理具有巨大动态范围的数字.long double在x87 FPU中以本机方式实现.我怀疑128b双将在现代x86上以软件模式实现,因为没有硬件可以在硬件中进行计算.

有趣的是,连续执行许多浮点运算是很常见的,中间结果实际上并不存储在声明的变量中,而是存储在FPU寄存器中,充分利用了全部精度.这就是为什么比较:

double x = sin(0); if (x == sin(0)) printf("Equal!");
Run Code Online (Sandbox Code Playgroud)

不安全,不能保证工作(没有额外的开关).

广告.3.

根据您使用的精度,会对速度产生影响.您可以使用以下命令更改使用FPU的精度:

void 
set_fpu (unsigned int mode)
{
  asm ("fldcw %0" : : "m" (*&mode));
}
Run Code Online (Sandbox Code Playgroud)

对于较短的变量,速度会更快,速度更慢.128位双打可能会在软件中完成,所以会慢得多.

它不仅浪费了RAM内存,而且浪费了缓存.从64b double到80位双倍将从33%(32b)浪费到几乎50%(64b)的内存(包括缓存).

广告4.

另一方面,据我所知,long double类型与-mfpmath = sse互斥,因为SSE中没有"扩展精度"这样的东西.另一方面,__ floatat8应该与SSE数学完全一致(尽管没有四精度指令肯定不在1:1指令基础上).在这些假设下我是否正确?

FPU和SSE单元完全分开.您可以在SSE的同时使用FPU编写代码.问题是,如果限制它仅使用SSE,编译器将生成什么?它会尝试使用FPU吗?我一直在用SSE做一些编程,而GCC本身只生成一个SISD.你必须帮助它使用SIMD版本.__float128可能适用于每台机器,甚至是8位AVR uC.毕竟,这只是摆弄比特.

十六进制表示的80位实际上是20个十六进制数字.也许未使用的位来自某些旧操作?在我的机器上,我编译了你的代码,在长模式下只改变了20位:66b4e0d2-ec09c1d5-00007ffe-deadbeef

128位版本的所有位都在变化.看着objdump它看起来好像是在使用软件仿真,几乎没有FPU指令.

此外,LDBL_MAX似乎对long double和__float128都起+ inf作用.向LDBL_MAX添加或从LDBL_MAX中减去1.0E100或1.0E2000之类的数字会产生相同的位模式.到目前为止,我认为foo_MAX常量是保持最大的可表示数字而不是+ inf(显然不是这样的情况?).

这似乎很奇怪......

我也不太确定一个80位的数字怎么可能被认为是一个128位值的+ inf ...也许我在一天结束时太累了并且做错了什么.

它可能正在扩展.在80位中被识别为+ inf的模式也被转换为128位浮点数中的+ inf.

  • 为什么带有`sin(0)`的代码不安全?是不是'sin(0)`精确为零,并且不是零提升到任何其他浮点类型仍然精确为零? (3认同)
  • 将"1E2000L"添加到"LDBL_MAX"并返回"LDBL_MAX"并不奇怪.当"LDBL_MAX"超过"1E4932L"时,"1E2000L"比**大**小**. (2认同)

sup*_*cat 6

IEEE-754 定义了 32 位和 64 位浮点表示以实现高效数据存储,并定义了 80 位表示以实现高效计算。目的是通过将参数转换为 80 位浮点值,将它们相加,并将结果转换回 64 位浮点类型来执行给定float f1,f2; double d1,d2;的语句d1=f1+f2+d2;。与直接对其他浮点类型执行操作相比,这将提供三个优势:

  1. 虽然与 32 位类型和 64 位类型之间的转换需要单独的代码或电路,但只需要一个“加”实现、一个“乘”实现、一个“平方根”实现,等等。

  2. 尽管在极少数情况下,使用 80 位计算类型可能会产生比直接使用其他类型稍微差一点的结果(最坏情况的舍入误差为 513/1024ulp,在其他类型上的计算会产生 511/1024ulp 的错误的情况下) ),采用80位的类型链式计算频繁地会更准确-有时多少更精确-比使用其他类型的计算。

  3. 在没有 FPU 的系统上,double在执行计算之前将 a 分离为单独的指数和尾数,对尾数进行归一化,并将单独的尾数和指数转换为double,这有点耗时。如果一个计算的结果将用作另一个计算的输入并被丢弃,则使用未打包的 80 位类型将允许省略这些步骤。

然而,为了使这种浮点数学方法有用,代码必须能够以与计算中使用的精度相同的精度存储中间结果,这样temp = d1+d2; d4=temp+d3;将产生与 相同的结果d4=d1+d2+d3;。据我所知,目的long double成为那种类型。不幸的是,尽管 K&R 将 C 设计为所有浮点值都以相同的方式传递给可变参数方法,但 ANSI C 打破了这一点。在最初设计的 C 中,给定 code float v1,v2; ... printf("%12.6f", v1+v2);,该printf方法不必担心是否v1+v2会产生 afloat或 a double,因为无论如何结果都会被强制转换为已知类型。此外,即使类型v1v2更改为double,该printf语句不必更改。

然而,ANSI C 要求调用的代码printf必须知道哪些参数是double,哪些是long double;很多代码(如果不是大多数)使用long double但写在平台上的代码与double未能使用正确的long double值格式说明符是同义词。long double除了作为可变参数方法参数传递时,它不是80 位类型,在这种情况下它将被强制为 64 位,许多编译器决定 makelong doubledouble并且不提供任何存储中间计算结果的方法。由于使用扩展精度类型进行计算只有在程序员可以使用该类型时才有好处,因此许多人得出结论认为扩展精度是邪恶的,即使只是 ANSI C 未能明智地处理可变参数导致其出现问题。

long doublePS--如果还有 a long floatwhich 被定义为float可以最有效地提升参数的类型,那么预期的目的将受益;在许多没有浮点单元的机器上,可能是 48 位类型,但最佳大小的范围可以从 32 位(在具有直接进行 32 位数学运算的 FPU 的机器上)到 80(在使用IEEE-754 设想的设计)。不过现在太晚了。