39

对于浮点数(即floatdoublelong double)是否具有一个且只有一个精度值,或者具有可以变化的精度值,我不断得到不同的答案。

一个名为浮点与双精度的主题似乎暗示浮点精度是绝对的。

但是,另一个名为float 和 double 之间的区别的主题说,

通常,双精度具有15 到 16位十进制数字

另一个消息来源说,

float 类型的变量通常具有大约7 位有效数字的精度

double 类型的变量通常具有大约16 位有效数字的精度

如果我使用的敏感代码在我的值不准确时很容易中断,我不喜欢参考上述近似值。因此,让我们直截了当。浮点精度是可变的还是不变的,为什么?

4

10 回答 10

29

精度是固定的,对于双精度,它正好是 53 个二进制数字(如果我们排除隐式前导 1,则为 52)。大约有 15 个十进制数字


OP要求我详细说明为什么正好有53个二进制数字意味着“大约”15个十进制数字。

为了直观地理解这一点,让我们考虑一种不太精确的浮点格式:我们将使用 4 位尾数,而不是像双精度数字那样的 52 位尾数。

因此,每个数字看起来像:(-1) s × 2 yyy × 1.xxxx(其中s是符号位,yyy是指数,1.xxxx是归一化尾数)。对于直接讨论,我们将只关注尾数而不是符号或指数。

这是1.xxxx所有xxxx值的表格(所有舍入都是半偶数,就像默认浮点舍入模式的工作原理一样):

  xxxx  |  1.xxxx  |  value   |  2dd  |  3dd  
--------+----------+----------+-------+--------
  0000  |  1.0000  |  1.0     |  1.0  |  1.00
  0001  |  1.0001  |  1.0625  |  1.1  |  1.06
  0010  |  1.0010  |  1.125   |  1.1  |  1.12
  0011  |  1.0011  |  1.1875  |  1.2  |  1.19
  0100  |  1.0100  |  1.25    |  1.2  |  1.25
  0101  |  1.0101  |  1.3125  |  1.3  |  1.31
  0110  |  1.0110  |  1.375   |  1.4  |  1.38
  0111  |  1.0111  |  1.4375  |  1.4  |  1.44
  1000  |  1.1000  |  1.5     |  1.5  |  1.50
  1001  |  1.1001  |  1.5625  |  1.6  |  1.56
  1010  |  1.1010  |  1.625   |  1.6  |  1.62
  1011  |  1.1011  |  1.6875  |  1.7  |  1.69
  1100  |  1.1100  |  1.75    |  1.8  |  1.75
  1101  |  1.1101  |  1.8125  |  1.8  |  1.81
  1110  |  1.1110  |  1.875   |  1.9  |  1.88
  1111  |  1.1111  |  1.9375  |  1.9  |  1.94

你说这提供了多少个十进制数字?您可以说 2,因为涵盖了两位小数范围内的每个值,尽管不是唯一的;或者您可以说 3,它涵盖所有唯一值,但不涵盖三位十进制数字范围内的所有值。

为了论证,我们会说它有 2 个十进制数字:十进制精度将是可以表示这些十进制数字的所有值的位数。


好的,那么,如果我们将所有数字减半(所以我们使用yyy= -1)会发生什么?

  xxxx  |  1.xxxx  |  value    |  1dd  |  2dd  
--------+----------+-----------+-------+--------
  0000  |  1.0000  |  0.5      |  0.5  |  0.50
  0001  |  1.0001  |  0.53125  |  0.5  |  0.53
  0010  |  1.0010  |  0.5625   |  0.6  |  0.56
  0011  |  1.0011  |  0.59375  |  0.6  |  0.59
  0100  |  1.0100  |  0.625    |  0.6  |  0.62
  0101  |  1.0101  |  0.65625  |  0.7  |  0.66
  0110  |  1.0110  |  0.6875   |  0.7  |  0.69
  0111  |  1.0111  |  0.71875  |  0.7  |  0.72
  1000  |  1.1000  |  0.75     |  0.8  |  0.75
  1001  |  1.1001  |  0.78125  |  0.8  |  0.78
  1010  |  1.1010  |  0.8125   |  0.8  |  0.81
  1011  |  1.1011  |  0.84375  |  0.8  |  0.84
  1100  |  1.1100  |  0.875    |  0.9  |  0.88
  1101  |  1.1101  |  0.90625  |  0.9  |  0.91
  1110  |  1.1110  |  0.9375   |  0.9  |  0.94
  1111  |  1.1111  |  0.96875  |  1.   |  0.97

按照与以前相同的标准,我们现在处理 1 个十进制数字。所以你可以看到,根据指数,你可以有更多或更少的十进制数字,因为二进制和十进制浮点数不能干净地相互映射

相同的论点适用于双精度浮点数(带有 52 位尾数),只有在这种情况下,您会根据指数获得 15 位或 16 位十进制数字。

于 2015-05-29T19:14:04.060 回答
25

所有现代计算机都使用二进制浮点运算。这意味着我们有一个二进制尾数,通常 24 位用于单精度,53 位用于双精度,64 位用于扩展精度。(扩展精度在 x86 处理器上可用,但在 ARM 或其他类型的处理器上不可用。)

24、53 和 64 位尾数意味着对于 2 k和 2 k+1之间的浮点数,下一个更大的数分别是 2 k-23、 2 k-52和 2 k-63。这就是决议。每个浮点运算的舍入误差最多是它的一半。

那么这如何转化为十进制数呢? 这取决于.

取 k = 0 且 1 ≤ x < 2。分辨率为 2 -23、2 -52和 2 -63,分别约为 1.19×10 -7、2.2×10 -16和 1.08×10 -19。这比 7、16 和 19 位小数少一点。然后取 k = 3 和
8 ≤ x < 16。两个浮点数之间的差异现在大了 8 倍。对于 8 ≤ x < 10,您分别得到略多于 6、小于 15 和略多于 18 位小数。但是对于 10 ≤ x < 16,你会多得到一位小数!

如果 x 仅略小于 2 k+1且仅略大于 10 n,则您将获得最多的小数位数,例如 1000 ≤ x < 1024。如果 x 只是 a,您将获得最少的小数位数位高于 2 k且位小于 10 n,例如11024 ≤ x < 11000。相同的二进制精度可以产生最多变化 1.3 位或 log 10 (2×10) 的十进制精度。

当然,您可以阅读文章“每个计算机科学家都应该了解的浮点运算知识”。

于 2015-05-29T19:45:15.150 回答
9

80x86 代码使用其硬件协处理器(最初是 8087)提供三个精度级别:32 位、64 位和 80 位。那些非常接近 1985 年的IEEE-754 标准。最近的标准指定了128 位格式。浮点格式有 24、53、65 和 113 个尾数位,对应于 7.22、15.95、19.57 和 34.02 位精度的十进制数字。

公式是 mantissa_bits / log_2 10,其中 10 的对数基数为 2 是 3.321928095。

虽然任何特定实现的精度没有变化,但它可能会在浮点值转换为十进制时出现。请注意,该值0.1没有精确的二进制表示。它是一个重复的位模式(0.0001100110011001100110011001100...),就像我们习惯用十进制表示 0.3333333333333 接近 1/3。

许多语言通常不支持 80 位格式。一些 C 编译器可能会提供long double使用 80 位浮点数或 128 位浮点数。唉,它也可能使用 64 位浮点数,具体取决于实现。

NPU 具有 80 位寄存器,并使用完整的 80 位结果执行所有操作。在 NPU 堆栈中计算的代码受益于这种额外的精度。不幸的是,糟糕的代码生成——或者写得不好的代码——可能会通过将中间计算存储在 32 位或 64 位变量中来截断或舍入中间计算。

于 2015-05-29T20:15:20.617 回答
8

浮点精度是可变的还是不变的,为什么?

通常,给定相同 2 次幂范围内的任何数字,浮点精度是不变的 - 一个固定值。绝对精度随着每步的 2 次方而变化。在整个 FP 范围内,精度大约与幅度相关。将这种相对二进制精度与十进制精度相关联会导致在十进制DBL_DIG数字之间变化DBL_DECIMAL_DIG- 通常为 15 到 17。


什么是精度?对于 FP,讨论相对精度是最有意义的。

浮点数的形式为:

符号 * 有效数 * pow(base,exponent)

它们具有对数分布。在100.0 和 3000.0 之间(范围为 30 倍)与 2.0 和 60.0 之间浮点数一样多。无论底层存储表示如何,这都是正确的。

1.23456789e100具有与 大致相同的相对精度1.23456789e-100


大多数计算机实现doublebinary64。此格式具有 53 位二进制精度。

1.0 和 2.0 之间的n数字在 ((2.0-1.0)/pow(2,52) 中具有 1 部分的绝对精度。64.0
和 128.0 之间的数字也n具有 ((128.0- 64.0)/pow(2,52)。

即使是 2 次方之间的一组数字,也具有相同的绝对精度。

在 FP 数的整个正常范围内,这近似于统一的相对精度。

当这些数字表示为十进制时,精度会摆动:数字 1.0 到 2.0 的绝对精度比数字 2.0 到 4.0 多 1 位。比 4.0 到 8.0 多 2 位,等等。

C 提供DBL_DIG, DBL_DECIMAL_DIG, 以及它们floatlong double对应物。 DBL_DIG表示最小相对小数精度。 DBL_DECIMAL_DIG可以认为是最大相对小数精度。

通常,这意味着给定double的精度为 15 到 17 位十进制数字。

考虑1.0它的下一个可表示double的,直到第 17 位有效十进制数字才改变。每个下一个doublepow(2,-52)或大约2.2204e-16分开。

/*
1 234567890123456789 */
1.000000000000000000...
1.000000000000000222...

现在考虑"8.521812787393891"将其下一个可表示的数字视为使用 16 个有效十进制数字的十进制字符串。这两个字符串,转换为double相同 8.521812787393891142073699...即使它们在第 16 位不同。说这double有 16 位精度被夸大了。

/*
1 234567890123456789 */
8.521812787393891
8.521812787393891142073699...
8.521812787393892
于 2015-05-29T20:01:12.523 回答
6

不,它是可变的。起点是非常薄弱的​​ IEEE-754 标准,它只确定了存储在内存中的浮点数的格式。单精度可以使用 7 位精度,双精度可以使用 15 位。

但该标准的一个主要缺陷是它没有指定如何执行计算。还有问题,尤其是Intel 8087浮点处理器,让程序员们彻夜难眠。该芯片的一个重大设计缺陷是它存储的浮点值内存格式多。80 位而不是 32 位或 64 位。该设计选择背后的理论是,这允许中间计算更精确并减少舍入误差。

听起来是个好主意,但在实践中效果并不好。编译器编写者将尝试生成代码,使中间值尽可能长时间地存储在 FPU 中。对代码速度很重要,将值存储回内存是昂贵的。麻烦的是,他经常必须将值存储回来,FPU 中的寄存器数量是有限的,并且代码可能会跨越函数边界。此时值会被截断并失去很多精度。对源代码的微小更改现在可以产生截然不同的值。此外,程序的非优化构建会产生与优化的不同的结果。以一种完全无法诊断的方式,您必须查看机器代码才能知道结果为何不同。

英特尔重新设计了他们的处理器来解决这个问题,SSE 指令集使用与内存格式相同的位数进行计算。然而,慢慢赶上,重新设计编译器的代码生成器和优化器是一项重大投资。三大 C++ 编译器都已切换。但是例如 .NET Framework 中的 x86 抖动仍然会生成 FPU 代码,它总是会生成。


然后存在系统误差,作为转换和计算的不可避免的副作用而失去精度。首先转换,人类以 10 为基数工作,但处理器使用以 2 为基数的数字。我们使用的漂亮整数,如 0.1 不能在处理器上转换为漂亮的整数。0.1 作为 10 的幂和是完美的,但没有产生相同值的 2 的有限幂和。转换它会产生无限数量的 1 和 0,其方式与您无法完美记下 10 / 3 的方式相同。因此需要将其截断以适应处理器,并产生一个偏离 +/- 0.5 位的值十进制值。

并且计算会产生错误。乘法或除法将结果中的位数加倍,将其四舍五入以使其重新适合存储的值会产生 +/- 0.5 位错误。减法是最危险的操作,会导致很多有效数字的丢失。例如,如果您计算 1.234567f - 1.234566f,则结果只剩下 1 个有效数字。这是一个垃圾结果。将具有几乎相同值的数字之间的差异求和在数值算法中是非常常见的。

获得过多的系统性错误最终是数学模型中的一个缺陷。举个例子,你永远不想使用高斯消元法,它对精度非常不友好。并且总是考虑另一种方法,LU Decomposition 是一种很好的方法。然而,数学家参与建立模型并解释结果的预期精度并不常见。像《数值食谱》这样的普通书也没有对精确度给予足够的关注,尽管它通过提出更好的模型间接地引导你远离糟糕的模型。最后,程序员经常会遇到问题。好吧,这很容易,然后任何人都可以做到,而我将失去一份高薪工作:)

于 2015-06-06T19:50:42.020 回答
5

浮点变量的类型定义了值的范围以及可以表示多少小数位 (!)。由于十进制和二进制分数之间没有整数关系,因此十进制分数实际上是一个近似值。

第二:另一个问题是执行精度算术运算。想想1.0/3.0还是PI。这样的值不能用有限的数字来表示——既不是十进制的,也不是二进制的。所以这些值必须四舍五入以适应给定的空间。可用的小数位数越多,精度越高。

现在考虑应用多个此类操作,例如 PI/3.0 。这将需要四舍五入:PI 本身并不准确,结果也不准确。这将失去精度两次,如果重新进行它会变得更糟。

所以,回到floatand double:float根据标准(C11,附件 F,其余部分),可用的位数更少,因此 roundig 的精度将低于 for double。试想一个小数点有 2 个小数位(m.ff,称为 float)和一个有四个(m.ffff,称为 double)。如果所有计算都使用双精度数,则在结果只有 2 个正确小数位之前,您可以进行更多操作,而不是已经从浮点数开始,即使浮点数结果就足够了。

请注意,在 ARM Cortex-M4F 等某些(嵌入式)CPU 上,硬件 FPU 仅支持 folat(单精度),因此双精度运算的成本会高得多。其他的MCU根本没有硬件浮点计算器,所以必须用我的软件来模拟(非常昂贵)。在大多数 GPU 上,float 的执行成本也比 double 便宜得多,有时甚至是 10 倍以上。

于 2015-05-29T19:30:56.787 回答
5

正如其他答案所解释的那样,存储具有精确的二进制数字计数。

要知道的一件事是,CPU 可以在内部以不同的精度运行操作,例如 80 位。这意味着这样的代码可以触发:

void Kaboom( float a, float b, float c ) // same is true for other floating point types.
{
    float sum1 = a+b+c;
    float sum2 = a+b;
    sum2 += c; // let's assume that the compiler did not keep sum2 in a register and the value was write to memory then load again.
    if (sum1 !=sum2)
        throw "kaboom"; // this can happen.
}

更复杂的计算更有可能。

于 2015-05-29T20:32:03.393 回答
4

我将在这里添加另类的答案,并说由于您已将此问题标记为 C++,因此无法保证浮点数据的精度。绝大多数实现在实现其浮点类型时使用IEEE-754 ,但这不是必需的。C++ 语言唯一需要的是(C++ 规范§3.9.1.8):

有三种浮点类型:float、double 和 long double。double 类型提供的精度至少与 float 一样,long double 类型提供的精度至少与 double 一样。float 类型的值集是 double 类型的值集的子集;double 类型的值集是 long double 类型的值集的子集。浮点类型的值表示是实现定义的。整数和浮点类型统称为算术类型。标准模板 std::numeric_limits (18.3) 的特化应指定实现的每种算术类型的最大值和最小值。
于 2015-05-29T19:41:03.423 回答
3

存储 a 所需的空间量float将是恒定的,同样 a double; 但是,相对而言,有用精度的数量通常会在 2 23分之一和 2 24分之一之间变化float,或者 2 52和 2 53分之一之间double。非常接近零的精度并不是那么好,第二小的正值是最小值的两倍,而后者又将无限大于零。然而,在大部分范围内,精度将如上所述变化。

请注意,虽然在整个范围内,相对精度变化小于两倍的类型通常是不切实际的,但精度的变化有时会导致计算产生的计算精度远低于其应有的精度。例如,考虑16777215.0f + 4.0f - 4.0f。所有值都可以float使用相同的比例精确表示,最接近大值的值是 16,777,215 中的 +/- 一部分,但第一次加法会float在值由一部分分隔的部分范围内产生结果仅 8,388,610,导致结果四舍五入为 16,777,220。因此,减去 4 得到 16,777,216 而不是 16,777,215。对于floatnear的大多数值16777216,加法4.0f和减法4.0f将产生不变的原始值,但在转折点处改变精度会导致结果在最低位置偏移一个额外的位。

于 2015-05-30T19:44:18.947 回答
0

好吧,这个问题的答案很简单但很复杂。这些数字以二进制形式存储。根据它是浮点数还是双精度数,计算机使用不同数量的二进制来存储数字。您获得的精度取决于您的二进制文件。如果您不知道二进制数是如何工作的,最好查一下。但简单地说,有些数字比其他数字需要更多的 1 和 0。

所以精度是固定的(相同数量的二进制数字),但您获得的实际精度取决于您使用的数字。

于 2015-05-29T19:17:33.743 回答