为什么浮点数据类型的精度不与其大小成正比?例如:
std::cout << sizeof(float) << "\n"; // this gives 4 on my machine "debian 64 bit" with "gcc 6.3.0"
std::cout << std::numeric_limits<float>::digits10 << "\n"; // gives 6
std::cout << sizeof(double) << "\n"; // gives 8
std::cout << std::numeric_limits<double>::digits10 << "\n"; // gives 15
std::cout << sizeof(long double) << "\n"; // gives 16
std::cout << std::numeric_limits<long double>::digits10 << "\n"; // gives 18
如您所见,double
的精度大约是float
的两倍,这是有道理的,因为double
的大小是float
的两倍
但是double
和long double
之间的情况不同,long double
的大小为128位,是64位double
的两倍,但是它的精度只有三位数!
我不知道如何实现浮点数,但是从一个合理的角度来看,仅以三位精度使用多于64位的内存甚至有意义吗?!
我四处搜寻,但找不到简单直接的答案。
如果有人可以解释为什么long double
的精度仅比double
高三位数,那么您是否也可以解释为什么与double
和float
之间的精度不同呢? / p>
我还想知道如何在不定义自己的数据类型的情况下获得更好的精度,而数据类型显然会牺牲性能?
答案 0 :(得分:2)
“精度”不只是浮点值。这也与“幅度”有关(虽然不确定该术语是否正确!):表示的值可以变成多大(或小)?
为此,请尝试同时打印每种类型的max_exponent
:
std::cout << "float: " << sizeof(float) << "\n";
std::cout << std::numeric_limits<float>::digits << "\n";
std::cout << std::numeric_limits<float>::max_exponent << "\n";
std::cout << "double: " << sizeof(double) << "\n";
std::cout << std::numeric_limits<double>::digits << "\n";
std::cout << std::numeric_limits<double>::max_exponent << "\n";
std::cout << "long double: " << sizeof(long double) << "\n";
std::cout << std::numeric_limits<long double>::digits << "\n";
std::cout << std::numeric_limits<long double>::max_exponent << "\n";
在ideone上的输出:
float: 4
24
128
double: 8
53
1024
long double: 16
64
16384
因此,多余的位并不能全部用于表示更多的数字(精度),但可以使指数更大。使用IEE 754 long double
中的措词通常会增加指数范围,而不是精度。
上面我的ideone样例所示的格式显示了(可能)"x86 extended precision format",它为整数部分分配1位,为小数部分分配63位(共64位)和15位(2 ^( 15-1)= 16384,用于指数的符号的1位)。
请注意,C ++标准仅要求long double
至少与double
一样精确,因此long double
可以是double
的同义词,所示的x86扩展为精度格式(最有可能)或更好(PowerPC上仅AFAIK GCC)。
我还想知道如何在不定义自己的数据类型的情况下获得更好的精度,而数据类型显然会牺牲性能?
您需要自己编写(一定要有学习经验,最好不要编写生产代码)或使用诸如GNU MPFR或Boost.Multiprecision之类的库。
答案 1 :(得分:1)
C ++标准没有为浮点类型设置固定要求,除了必须满足的最低要求。
就像您正在使用的C ++实现一样,它以Intel处理器为目标。除了常见的IEEE-754基本32位和64位二进制浮点格式,Intel还具有80位格式。您的C ++实现可能会将其用于long double
。
英特尔的80位格式的有效位数比64位double
的格式多11位。 (实际上,它使用64,而double
格式使用52,但是其中一个保留用于显式前导1。)多11位意味着2 11 = 2048倍有效值,大约还有三位十进制数字。
80位格式(十个字节)优先与16字节的倍数对齐,因此包含六个字节的填充以使long double
大小为16字节的倍数。
答案 2 :(得分:1)
您的问题中有许多不正确的假设
首先,对于C ++中的类型大小没有要求。该标准仅规定每种类型的最低精度,并且...
...类型
double
的精度至少与float
相同,而类型long double
的精度至少与double
相同。类型float
的值集是类型double
的值集的子集;double
类型的值集是long double
类型的值集的子集。浮点类型的值表示形式是实现定义的。http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf
大多数现代实现都将float
和double
映射到IEEE-754单精度和双精度格式,因为对它们的硬件支持是主流。但是long double
没有这么广泛的支持,因为很少有人需要比双精度更高的精度,而那些硬件的成本要高得多。因此,某些平台将其映射到IEEE-754双精度,即与double
相同。如果基础硬件支持,则另一些映射到80-bit IEEE 754 extended-precision format。否则,long double
将由double-double
arithmetic或IEEE-754 quadruple-precision
此外,精度不能线性缩放为该类型的位数。可以很容易地看出,double
的精确度是{{1}的两倍以上,并且比float
的范围宽8倍,尽管存储量只有两倍,因为它有53个有效位为24位浮点数和3位以上的指数位。类型也可以具有trap representations或填充位,因此即使它们具有相同的大小并属于相同的类别(整数或浮点),不同的类型也可能具有不同的范围
所以这里重要的是float
。如果打印,您将看到std::numeric_limits<long double>::digits
具有有效位的64位,仅比long double
多11位。 See it live。这意味着您的编译器对double
使用80位扩展精度,其余的只是填充字节来保持对齐。实际上gcc has various options会更改您的输出:
long double
和-malign-double
用于控制-mno-align-double
的对齐方式long double
和-m96bit-long-double
用于更改填充大小-m128bit-long-double
,-mlong-double-64
和-mlong-double-80
用于控制基础的-mlong-double-128
实现通过更改选项,您将获得long double
-mlong-double-128
:大小= 16,数字10 = 33,数字2 = 113 -m96bit-long-double
:大小= 12,数字10 = 18,数字2 = 64 -mlong-double-64
:大小= 8,数字10 = 15,数字2 = 53 如果禁用填充,您将获得size = 10,但这会因未对齐而导致性能损失
在PowerPC中,您也可以see the same phenomena when changing the floating-point format。使用long double
(双精度双精度算术,这是默认设置),您将获得(size,digits10,digits2)=(16,31,106),但是使用-mabi=ibmlongdouble
时,元组将变为(16, 33,113)
有关更多信息,您应该阅读https://en.wikipedia.org/wiki/Long_double
我还想知道如何在不定义自己的数据类型的情况下获得更高的精度
要搜索的关键字是arbitrary-precision arithmetic。在List of arbitrary-precision arithmetic software中可以找到各种库。您可以在标签bigint,biginteger或arbitrary-precision
中找到更多信息。