我在GCC/x86中寻找关于long double
和__float128
的详细信息(更多的是出于好奇,而不是因为实际问题)。
可能很少有人会需要这些(我只是,有史以来第一次,* 真正 * 需要一个double
),但我想它仍然是值得的(和有趣的)知道你在你的工具箱里有什么,它是关于什么的。
有鉴于此,请原谅我有些开放性的问题:
1.有人能解释一下这些类型的实现原理和预期用途吗?例如,它们是“尴尬的实现”,因为标准允许类型,如果它们只是与double
相同的精度,有人可能会抱怨,还是它们预期作为一流的类型?
1.或者,有人有一个好的,可用的网络参考分享?一个谷歌搜索"long double" site:gcc.gnu.org/onlinedocs
没有给予我很多真正有用的。
1.假设“如果您认为您需要双精度,那么您可能不了解浮点”这一常见说法并不适用,即您“确实”需要比float
更高的精度,而不管是8字节还是16字节的内存被烧毁...是否可以期望直接跳转到long double
或__float128
而不是double
,而不会显著影响性能?
1.当值在内存和寄存器之间移动时,Intel CPU的“扩展精度”特性一直是令人讨厌的意外的来源。如果实际上存储96位,long double
类型应该可以消除这个问题。另一方面,我知道long double
类型与-mfpmath=sse
是互斥的,另一方面,__float128
在SSE数学中应该可以完美地工作(尽管在没有四倍精度指令的情况下,在1:1指令库上肯定不行)。我的这些假设对吗?
(3.和4.可能可以通过花在分析和反汇编上的一些工作来弄清楚,* 但也许其他人以前也有同样的想法,并且已经做了这项工作 *。)
背景(这是TL; DR部分):
我最初在long double
上遇到了一个问题,因为我在<float.h>
中查找DBL_MAX
,而碰巧LDBL_MAX
在下一行。“哦,看,GCC实际上有128位双精度浮点数,并不是说我需要它们,但是......很酷”是我的第一个想法。惊讶吧,惊讶吧:sizeof(long double)
返回12...等等,您是说16?
C和C标准并没有给予非常具体的类型定义。C99公式6.2.5 10说明double
的数是long double
的子集,而C03说明(3.9.1 8)long double
至少具有与double
一样高精度(这是一样的,只是措辞不同)。基本上,这些标准将一切都留给实现,与long
、int
和short
中的一个或多个。
Wikipedia说GCC使用 “x86处理器上的80位扩展精度,而不管使用的物理存储”。
GCC文档在同一页上指出,由于i386 ABI,类型的大小为96位,但任何选项启用的精度都不超过80位(哈?什么?),奔腾和更新的处理器也希望它们被对齐为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;
}
当使用long double
时,输出看起来有点像这样,其中标记的数字是常数,而所有其他数字最终会随着数字越来越大而变化:
5636666b-c03ef3e0-00223fd8-deadbeef
^^ ^^^^^^^^
这意味着它不是一个80位数。一个80位数有18个十六进制数字。我看到22个十六进制数字变化,看起来更像一个96位数(24个十六进制数字)。它也不是一个128位数,因为0xdeadbeef
没有被修改,这与sizeof
返回12是一致的。__int128
的输出看起来实际上只是一个128位的数字,所有的位最终都会翻转。
使用-m128bit-long-double
编译不会将long double
对齐到128位,并使用32位零填充,如文档所示。它也没有使用__int128
,但确实看起来对齐到128位,并使用值0x7ffdd000
(?!)填充。
此外,对于long double
和__float128
,LDBL_MAX
看起来像+inf
一样工作。将1.0E100
或1.0E2000
之类的数字加到LDBL_MAX
或从LDBL_MAX
减去1.0E100
或从1.0E2000
减去1.0E100
之类的数字导致相同的位模式。
到目前为止,我一直认为foo_MAX
常量是 * 而不是 * +inf
的最大可表示数(显然情况不是这样?)。我也不太确定一个80位的数如何可以想象地充当128位值的+inf
......也许我只是在一天结束时太累了,做错了什么。
4条答案
按热度按时间7hiiyaii1#
第1段。
这些类型被设计用来处理动态范围很大的数字。长双精度浮点数在x87 FPU中以原生方式实现。我怀疑128 b双精度浮点数将在现代x86上以软件模式实现,因为没有硬件来执行硬件中的计算。
有趣的是,在一行中执行多个浮点运算是很常见的,中间结果实际上并不存储在声明的变量中,而是存储在FPU寄存器中,以利用全精度。这就是为什么比较:
不安全,不能保证工作(没有额外的开关)。
第3段。
根据您使用的精度,速度会受到影响。您可以使用以下命令更改FPU的精度:
对于较短的变量它会更快,对于较长的变量它会更慢。128位双精度可能会在软件中完成,所以会慢得多。
这不仅是RAM内存的浪费,也是缓存的浪费。从64 b的双倍变成80位的双倍,将浪费33%(32 b)到几乎50%(64 b)的内存(包括缓存)。
第4段。
另一方面,我知道long double类型与-mfpmath=sse是互斥的,因为在SSE中没有“扩展精度”这样的东西。另一方面,__float128在SSE数学中应该能很好地工作(尽管在没有四倍精度指令的情况下,在1:1指令库上肯定不行)。在这些假设下,我是对的吗?
FPU和SSE单元是完全分开的。你可以在写代码的同时使用FPU和SSE。问题是如果你限制编译器只使用SSE,编译器会生成什么?它会尝试使用FPU吗?我一直在用SSE做一些编程,GCC自己只会生成一个SISD。你必须帮助它使用SIMD版本。__float128可能在每台机器上都能工作。即使是8位AVR uC。它毕竟只是在摆弄位。
十六进制表示的80位实际上是20个十六进制数字。也许没有使用的位是来自一些旧的操作?在我的机器上,我编译了你的代码,只有20位在长模式下改变:66 b4 e0 d2-ec 09 c1 d5 - 00007烤牛肉
128位版本的所有位都发生了变化。看
objdump
它看起来就像是在使用软件仿真,几乎没有FPU指令。此外,LDBL_MAX似乎在long double和__float128中作为+inf工作。将1.0E100或1.0E2000之类的数字加到LDBL_MAX或从LDBL_MAX中减去1.0E100或1.0E2000会产生相同的位模式。到目前为止,我一直认为foo_MAX常量将保存不是+inf的最大可表示数(显然情况并非如此?)。
这似乎有些奇怪......
我也不太清楚一个80位的数字怎么可能作为一个128位的值的+inf...也许我只是在一天结束的时候太累了,做错了什么。
它可能被扩展了。在80位中被识别为+inf的模式也被翻译成了128位浮点中的+inf。
ozxc1zmp2#
IEEE-754定义了32位和64位浮点数表示法以实现有效的数据存储,并定义了80位浮点数表示法以实现有效的计算。其目的是给定
float f1,f2; double d1,d2;
,通过将自变量转换为80位浮点数值,并将它们相加,然后将结果转换回64位浮点类型。与直接在其他浮点类型上执行操作相比,这将提供三个优点:1.尽管需要单独的代码或电路来进行32位类型和64位类型之间的转换,但仅需要一个“加”实现、一个“乘”实现、一个“平方根”实现等。
1.尽管在极少数情况下,使用80位计算类型所产生的结果可能比直接使用其他类型所产生的结果的准确性稍低(在对其它类型的计算将产生511/1024 ulp的误差的情况下最坏情况舍入误差是513/1024 ulp),使用80位类型的链式计算通常会比使用其他类型的计算更准确--有时 * 更 * 准确。
1.在没有FPU的系统上,在执行计算之前将
double
分离为单独的指数和尾数、将尾数归一化以及将单独的尾数和指数转换为double
,这些操作都比较耗时。如果一个计算的结果将用作另一个计算的输入并被丢弃,则使用未压缩的80位类型将允许省略这些步骤。然而,为了使这种浮点数学方法有用,代码必须能够以与计算中使用的精度相同的精度存储中间结果,这样
temp = d1+d2; d4=temp+d3;
将产生与d4=d1+d2+d3;
相同的结果。据我所知,long double
的目的是 * 成为 * 那种类型。不幸的是,即使K&R在设计C时将所有浮点值都以相同的方式传递给可变变量方法,ANSIC却打破了这一点。在最初设计的C中,给定代码float v1,v2; ... printf("%12.6f", v1+v2);
,printf
方法不必担心v1+v2
将生成float
还是double
,因为无论如何结果都将被强制为已知类型。此外,即使v1
或v2
的类型更改为double
,printf
语句也不必更改。然而,ANSI C要求调用
printf
的代码必须知道哪些参数是double
,哪些参数是long double
;在使用long double
但与double
同义的平台上编写的许多代码(如果不是大多数的话)无法使用long double
值的正确格式说明符,许多编译器决定使long double
与double
同义,并且不提供任何存储中间计算结果的方法.由于使用扩展精度类型进行计算只有在程序员可以使用该类型时才是好的,许多人得出结论认为扩展精度是不好,尽管只是ANSI C未能合理地处理可变参数才使它有问题。PS-如果也有一个
long float
,该long float
被定义为float
参数可以被最有效地提升到的类型,则long double
的预期目的将是有益的;在许多没有浮点单元的机器上,浮点单元可能是48位类型,但最佳大小的范围可以从32位(在具有直接执行32位数学运算的FPU的机器上)到80位(在使用IEEE-754设想的设计的机器上)。cetgtptt3#
它可以归结为4.9999999999999999999和5.0之间的差异。
1.虽然范围是主要差异,但重要的是精度。
1.这些类型的数据将需要在大圆计算或坐标数学中,可能与GPS系统一起使用。
1.由于精度比普通的双精度高得多,这意味着您可以保留通常18个有效数字,而不会失去计算的准确性。
1.我认为扩展精度使用80位(主要用于数学处理器),因此128位将更准确。
vzgqcmou4#
C99和C++11增加了
float_t
和double_t
类型,它们是内置浮点类型的别名。粗略地说,float_t
是在float
类型的值之间进行算术运算的结果的类型,而double_t
是在double
类型的值之间进行算术运算的结果的类型。