C ++中的浮点数据类型的精度

时间:2019-04-19 20:16:33

标签: c++ gcc floating-point precision long-double

为什么浮点数据类型的精度不与其大小成正比?例如:

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的两倍

但是doublelong double之间的情况不同,long double的大小为128位,是64位double的两倍,但是它的精度只有三位数!

我不知道如何实现浮点数,但是从一个合理的角度来看,仅以三位精度使用多于64位的内存甚至有意义吗?!

我四处搜寻,但找不到简单直接的答案。 如果有人可以解释为什么long double的精度仅比double高三位数,那么您是否也可以解释为什么与doublefloat之间的精度不同呢? / p>

我还想知道如何在不定义自己的数据类型的情况下获得更好的精度,而数据类型显然会牺牲性能?

3 个答案:

答案 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 MPFRBoost.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

大多数现代实现都将floatdouble映射到IEEE-754单精度和双精度格式,因为对它们的硬件支持是主流。但是long double没有这么广泛的支持,因为很少有人需要比双精度更高的精度,而那些硬件的成本要高得多。因此,某些平台将其映射到IEEE-754双精度,即与double相同。如果基础硬件支持,则另一些映射到80-bit IEEE 754 extended-precision format。否则,long double将由double-double arithmeticIEEE-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

的以下结果

如果禁用填充,您将获得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中可以找到各种库。您可以在标签

中找到更多信息。