为什么浮点数不正确?


198

为什么有些数字存储为浮点数时会失去准确性?

例如,十进制数9.2可以精确地表示为两个十进制整数(92/10)之比,两个整数都可以精确地表示为二进制(0b1011100/0b1010)。但是,存储为浮点数的相同比率永远不会完全等于9.2

32-bit "single precision" float: 9.19999980926513671875
64-bit "double precision" float: 9.199999999999999289457264239899814128875732421875

这样一个看似简单的数字怎么会“太大”而无法在64位内存中表达呢?




Answers:


241

在大多数编程语言中,浮点数的表示非常像科学记数法:具有指数和尾数(也称为有效位数)。一个非常简单的数字,9.2实际上就是这个分数:

5179139571476070 * 2 -49

指数在哪里,-49尾数在哪里5179139571476070。无法以这种方式表示一些十进制数字的原因是,指数和尾数都必须是整数。换句话说,所有浮点数必须是整数乘以2整数次幂

9.2可以很简单92/10,但是如果n限制为整数,则10不能表示为2 n


看到数据

首先,使用一些函数来查看组成32位和64位的组件float。如果只关心输出,则可以查看以下内容(Python示例):

def float_to_bin_parts(number, bits=64):
    if bits == 32:          # single precision
        int_pack      = 'I'
        float_pack    = 'f'
        exponent_bits = 8
        mantissa_bits = 23
        exponent_bias = 127
    elif bits == 64:        # double precision. all python floats are this
        int_pack      = 'Q'
        float_pack    = 'd'
        exponent_bits = 11
        mantissa_bits = 52
        exponent_bias = 1023
    else:
        raise ValueError, 'bits argument must be 32 or 64'
    bin_iter = iter(bin(struct.unpack(int_pack, struct.pack(float_pack, number))[0])[2:].rjust(bits, '0'))
    return [''.join(islice(bin_iter, x)) for x in (1, exponent_bits, mantissa_bits)]

该函数背后有很多复杂性,并且很容易解释,但是如果您感兴趣的话,对于我们而言,重要的资源就是struct模块。

Python float是64位双精度数。在其他语言(例如C,C ++,Java和C#)中,双精度具有单独的类型double,通常将其实现为64位。

当我们以示例调用该函数时,9.2得到的是:

>>> float_to_bin_parts(9.2)
['0', '10000000010', '0010011001100110011001100110011001100110011001100110']

解释数据

您会看到我将返回值分为三个部分。这些组件是:

  • 标志
  • 指数
  • 尾数(也称为有效数或分数)

标志

该符号作为单个位存储在第一部分中。很容易解释:0意味着浮点数是一个正数;1表示负面。因为9.2为正,所以我们的符号值为0

指数

指数以11位存储在中间组件中。在我们的情况下,0b10000000010。以十进制表示,代表值1026。该组件的一个怪癖是必须减去一个等于2 (位数)的数字 1-1,以获得真实的指数。在我们的例子中,这意味着减去 0b1111111111(十进制数1023)以获得真实的指数0b00000000011(十进制数3)。

尾数

尾数作为52位存储在第三部分中。但是,此组件也有一个怪癖。要理解这一怪异现象,请考虑用科学计数法表示的数字,如下所示:

6.0221413x10 23

尾数将是6.0221413。回想一下,科学计数法中的尾数始终以单个非零数字开头。二进制也是如此,只是二进制只有两位数字:01。因此二进制尾数总是1!当存储浮点数时,将1省略二进制尾数的前面以节省空间。我们必须将其放回第三个元素的前面以获取真实的尾数:

1.0010011001100110011001100110011001100110011001100110110

这不仅仅涉及简单的加法,因为存储在我们的第三个分量中的位实际上代表了尾数的小数部分,即小数点右边。

在处理十进制数字时,我们通过乘以10的乘方或除以“移动小数点”。在二进制中,我们可以通过乘以2的乘方或除以进行相同的操作。由于我们的第三个元素有52位,因此我们除以通过2 52将其向右移动52个位置:

0.0010011001100110011001100110011001100110011001100110110

在十进制,这是同分6755399441055744503599627370496获得0.1499999999999999。(这是一个比率的示例,该比率可以精确地用二进制表示,但只能近似用十进制表示;有关更多详细信息,请参见:675539944105574/4503599627370496。)

现在,我们已经将第三个分量转换为分数,加法1给出了真实的尾数。

重新盖上组件

  • 符号(第一部分):0为正,1为负
  • 指数(中间部分):减去2 (#位) - 1 - 1获得真正的指数
  • 尾数(最后一个分量):除以2 (位数),然后加上1即可得到真实的尾数

计算数字

将所有三个部分放在一起,我们得到这个二进制数字:

1.0010011001100110011001100110011001100110011001100110 x 10 11

然后我们可以将其从二进制转换为十进制:

1.1499999999999999 x 2 3(不准确!)

并相乘以显示以(9.2)开头的数字的最终表示形式,然后将其存储为浮点值:

9.1999999999999993


表示为分数

9.2

现在我们已经建立了数字,可以将其重构为一个简单的分数:

1.0010011001100110011001100110011001100110011001100110 x 10 11

将尾数转换为整数:

10010011001100110011001100110011001100110011001100110 x 10 11-110100

转换为十进制:

5179139571476070 x 2 3-52

减去指数:

5179139571476070 x 2 -49

将负指数转化为除法:

5179139571476070/2 49

相乘指数:

5179139571476070/562949953421312

等于:

9.1999999999999993

9.5

>>> float_to_bin_parts(9.5)
['0', '10000000010', '0011000000000000000000000000000000000000000000000000']

您已经可以看到尾数只有4位数字,后面跟着很多零。但是,让我们逐步进行。

汇编二进制科学符号:

1.0011 x 10 11

移动小数点:

10011 x 10 11-100

减去指数:

10011 x 10 -1

二进制到十进制:

19 x 2 -1

负除法指数:

19/2 1

相乘指数:

19/2

等于:

9.5



进一步阅读


1
还有一个很好的教程,展示了如何走另一条路-给定数字的十进制表示形式,如何构造等效的浮点数。“长除法”方法非常清楚地表明,在尝试表示数字后,您如何最终得到“余数”。如果您想对答案真正“规范”,则应添加。
弗洛里斯2014年

1
如果您在谈论Python和浮点数,我建议您至少在链接中包含Python教程:docs.python.org/3.4/tutorial/floatingpoint.html 这应该是一站式的Python程序员浮点问题的资源。如果缺少某种方法(几乎可以肯定是),请在Python错误跟踪器上打开一个问题以进行更新或更改。
2014年

@mhlester如果这变成了社区Wiki,请随时将我的答案纳入您的答案。
Nicu Stiurca 2014年

5
这个答案肯定也应该链接到float-point-gui.de,因为它可能是初学者的最佳介绍。IMO,它甚至应该超越“每位计算机科学家应该知道的...”这些天,可以合理地理解Goldberg论文的人们通常已经很清楚了。
Daniel Pryden 2014年

1
“这是一个比率的示例,可以精确地用二进制表示,但只能近似用十进制表示”。这不是真的。所有这些“ 2的幂数”的比率均以十进制精确。为了方便起见,任何近似值都只是为了缩短小数点。
里克·里根

29

这不是一个完整的答案(mhlester已经涵盖了很多我不会重复的好地方),但是我想强调数字的多少取决于您所从事的工作。

考虑分数2/3

在good-ol'base 10中,我们通常将其写成类似

  • 0.666 ...
  • 0.666
  • 0.667

当我们查看这些表示形式时,即使只有第一个表示在数学上等于该分数,我们也倾向于将它们与分数2/3关联。第二和第三种表示法/近似值的误差约为0.001,实际上比9.2和9.1999999999999993之间的误差差很多。实际上,第二个表示甚至没有正确舍入!尽管如此,对于数字2/3的近似值,我们对0.666并没有问题,因此对于大多数程序中的9.2如何近似,我们不应该有任何问题(是的,在某些程序中很重要。)

数字基

因此,这里的基数至关重要。如果我们试图以3为基数表示2/3,则

(2/3)10 = 0.2 3

换句话说,通过切换基数,我们可以精确,有限地表示相同的数字!结论是,即使您可以将任何数字转换为任何基数,所有有理数在某些基数中都具有精确的有限表示,而在其他基数中却没有

为了说明这一点,让我们看一下1/2。即使这个非常简单的数字在10和2的底数中都有确切的表示,您可能会感到惊讶。

(1/2)10 = 0.5 10 = 0.1 2 = 0.1111 ... 3

为什么浮点数不正确?

因为经常出现,它们是近似的有理数,不能以2为底来有限地表示(数字重复),并且通常它们是近似的实数(可能是无理数),在任何基数中都不可能用有限的数来表示。


3
因此,换句话说,base-3非常适合1/3,就像base-10适合一样1/10。这两个分数都不适用于base-2
mhlester 2014年

2
@mhlester是的。通常,N分母对于分母为N或其倍数的任何分数都是理想的。
Nicu Stiurca 2014年

2
这就是为什么一些数值工具箱跟踪“什么除以什么”,并且在此过程中可以对所有有理数保持“无限精度”的原因之一。就像物理学家一样,他们的方程式直到最后可能的时刻都保持符号化,以防诸如此类的因素π抵消。
弗洛里斯2014年

3
@Floris我也看到过这样的情况:仅执行基本算术(即,保留输入的合理性),确定输入是否(可能)是有理数,使用常规浮点算术执行数学,然后重新估计有理数的算法在末尾近似以修复任何舍入误差。尤其是Matlab的简化行梯形形式算法可以做到这一点,并且极大地帮助了数值稳定性。
Nicu Stiurca 2014年

@SchighSchagh-有趣,我不知道。我确实知道,在当今双精度和双精度方面,数值稳定性尚不足以体现。这意味着许多人错过了学习许多精美算法的精髓。我真的很喜欢可以计算并纠正自己的错误的算法。
弗洛里斯2014年

13

尽管所有其他答案都不错,但仍然缺少一件事:

这是不可能的代表无理数(如π, ,sqrt(2)log(3)等)精确!

这就是为什么它们被称为非理性。世界上没有足够的位存储量来容纳其中之一。只有符号算术能够保留其精度。

尽管如果将数学需求限制为有理数,则只有精度问题可以解决。您将需要存储一对(可能非常大)整数,ab保留由分数表示的数字a/b。就像高中数学(例如a/b * c/d = ac/bd)一样,您所有的算术都必须对分数进行。

当然,你仍然会遇到同样的麻烦时pisqrtlogsin,等都有涉及。

TL; DR

对于硬件加速算术,只能表示有限数量的有理数。每个无法代表的数字都是近似值。无论系统如何,某些数字(即无理数)都无法表示。


4
有趣的是,确实存在非理性基础。Phinary,例如。
Veedrac 2014年

5
无理数可以(仅)以其基数表示。例如,pi在基数pi中为10
phuclv

4
点仍然有效:无论系统如何,某些数字都无法表示。更改基准不会获得任何收益,因为这样一来,其他数字将无法再显示。
LumpN 2014年

4

有无数个实数(以至于您无法枚举它们),有无数个有理数(可以枚举它们)。

浮点表示形式是有限的(就像计算机中的任何东西一样),因此不可避免地无法表示许多数字。特别是,64位仅允许您区分18,446,744,073,709,551,616个不同的值(与无穷大相比不算什么)。按照标准约定,9.2不是其中之一。对于某些整数m和e,可以是m.2 ^ e的形式。


您可能会想出一个不同的计算系统,例如基于10的计算系统,其中9.2将具有精确的表示形式。但是其他数字(例如1/3)仍然无法表示。


另请注意,双精度浮点数非常准确。它们可以表示范围很广的任意数字,最多可以有15个精确数字。对于日常生活计算,仅4位或5位数字就足够了。除非您要计算生命中的每一毫秒,否则您将永远不需要那15个。


1

为什么不能在二进制浮点数中表示9.2?

浮点数(略为简化)是一种具有有限位数和可移动小数点的位置编号系统。

如果分母的主要因子(当分数用最低的术语表示时)是基数,则分数只能使用位置编号系统中的有限位数精确表示。

10的素数是5和2,因此在基数10中,我们可以表示a /(2 b 5 c)形式的任何分数。

另一方面,2的唯一质数是2,因此在基数2中,我们只能表示a /(2 b)形式的分数

为什么计算机使用这种表示形式?

因为它是一种易于使用的格式,并且对于大多数用途来说都足够准确。基本上,科学家使用“科学记数法”并在每一步将其结果四舍五入为合理数量的数字是相同的原因。

当然可以定义一个小数格式,例如使用32位分子和32位分母。它将能够表示IEEE双精度浮点不能表示的数字,但是同样地,有许多数字可以用双精度浮点表示,而不能以这种固定大小的分数格式表示。

但是,最大的问题是,这种格式很难进行计算。有两个原因。

  1. 如果您希望每个数字都具有一个精确的表示形式,那么在每次计算之后,您都需要将分数减小为最低的一项。这意味着对于每个操作,您基本上都需要进行最大的除数计算。
  2. 如果在计算之后由于分子或分母而最终得出的结果无法表示,则需要找到最接近的可表示结果。这是不重要的。

某些语言确实提供分数类型,但通常它们结合精确度进行计算,这避免了担心近似分数的问题,但是当数经过分母和分母大小的大量计算步骤时,这会产生自己的问题。因此,馏分所需的存储量可能会爆炸。

某些语言还提供了十进制浮点类型,这些类型主要用于以下情况中:计算机获得的结果必须与人类已经编写的预先存在的舍入规则相匹配(很重要,财务计算很重要)。与二进制浮点数相比,使用它们要困难一些,但是最大的问题是大多数计算机都不为它们提供硬件支持。


-4

试试这个

DecimalFormat decimalFormat = new DecimalFormat("#.##");
String.valueOf(decimalFormat.format(decimalValue))));

decimalValue”是您转换的价值。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.