为什么浮点数不准确?

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

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

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

这样一个明显简单的数字如何能够在64位内存中“太大”expression?

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

5179139571476070 * 2 -49

指数为-49 ,尾数为5179139571476070 。 不可能用这种方式表示一些小数的原因是指数和尾数都是整数。 换句话说,所有的浮点数必须是乘以2整数次幂整数

9.2可以简单地为92/10 ,但是如果n被限制为整数值,则10不能被expression为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)] 

这个函数背后有很多复杂的东西,它可以解释的相切,但是如果你感兴趣的话,我们的目的的重要资源是结构模块。

Python的float是一个64位的双精度数字。 在诸如C,C ++,Java和C#等其他语言中,双精度有一个单独的typesdouble ,通常实现为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.0221413×10 23

尾数是6.0221413 。 回想一下,科学记数法中的尾数总是以一个非零数字开始。 对于二进制也是如此,除了二进制只有两个数字: 01 。 所以二进制尾数总是1开头! 当存储一个浮点数时,二进制尾数前面的1被省略以节省空间; 我们必须把它放回到第三个元素的前面来得到真正的尾数:

1.0010011001100110011001100110011001100110011001100110

这不仅仅是一个简单的加法,因为存储在第三个组件中的位实际上代表尾数的小数部分,在小数点的右边。

当处理十进制数时,我们通过乘以或除以10的幂来“移动小数点”。在二进制中,我们可以通过乘以或除以2的幂来做同样的事情。因为我们的第三元素具有52位,它通过2 52把它移动到右边的52个地方:

0.0010011001100110011001100110011001100110011001100110

在十进制表示法中,这与将675539944105574除以4503599627370496以得到0.1499999999999999 。 (这是一个比例的一个例子,可以精确地用二进制表示,但只能用十进制表示;更多细节请参见: 675539944105574/4503599627370496 。)

现在我们已经将第三个分量转换成分数了,加1就是真正的尾数。

回收组件

  • 符号(第一部分): 0表示正数, 1表示负数
  • 指数(中间分量):减2 (#位数) – 1 – 1得到真正的指数
  • 尾数(最后一个分量):除以2 (位数)并加1得到真正的尾数

计算数字

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

1.0010011001100110011001100110011001100110011001100110 x 10 11

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

1.1499999999999999 x 2 3 (不精确!)

然后乘以以浮点值forms存储起始数( 9.2 )的最终表示forms:

9.1999999999999993


表示为一个分数

9.2

现在我们已经build立了这个数字,可以把它重构成一个简单的部分:

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×10 11

移动小数点:

10011×10 11-100

减指数:

10011×10 -1

二进制到十进制:

19×2 -1

分裂的负指数:

19/2 1

乘法指数:

19/2

等于:

9.5



进一步阅读

  • 浮点指南:每个程序员应该知道浮点算术,或者,为什么我的数字不加起来? (floating-point-gui.de)
  • 计算机科学家应该知道什么是浮点运算 (Goldberg 1991)
  • IEEE双精度浮点格式 (Wikipedia)
  • 浮点算术:问题和局限性 (docs.python.org)
  • 浮点二进制

这不是一个完整的答案( mhlester已经涵盖了很多好的基础,我不会重复),但是我想强调一个数字的表示取决于你所在的基数。

考虑2/3分数

在良好的基础10,我们通常把它写成类似的东西

  • 0.666 …
  • 0.666
  • 0.667

当我们查看这些表示时,我们倾向于将它们中的每一个与分数2/3相关联,即使只有第一表示在math上等于分数。 第二个和第三个表示/近似值的误差在0.001的数量级上,实际上比9.2和9.1999999999999993之间的误差差得多。 事实上,第二个表示甚至不是正确的圆整! 尽pipe如此,我们对于2/3的近似值0.666没有任何问题, 所以我们在大多数程序中如何近似9.2应该没有问题 (是的,在一些程序中很重要。)

数字基地

所以这里的数字基地是crutial。 那么,如果我们试图以3比3代表2/3

(2/3) 10 = 0.2 3

换句话说,我们有一个确切的,有限的表示相同的数字切换基地! 即使你可以将任何数字转换成任何基数, 所有的有理数在某些基数上都有确切的有限表示,但是在其他基数上却没有

为了把这一点带回家,让我们看看1/2。 这可能会让你感到惊讶,即使这个非常简单的数字在基数10和2中有精确的表示,它需要在基数3中重复表示。

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

为什么浮点数不准确?

因为它们往往是近似于基数2(数字重复)无法有限表示的合理性,并且通常它们近似于在任何基数中可能无法表示的实数(可能无理数)。

虽然所有其他的答案都很好,但还是有一点缺失:

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

而这实际上就是为什么他们被称为非理性。 世界上没有多less位存储就足以保存其中的一个。 只有符号算术能够保持其精度。

虽然如果你将math的需求限制在理性的数字上,那么只有精度的问题变得易于处理。 你需要存储一对(可能非常大的)整数ab来保存分数a/b表示的数字。 所有的算术运算都必须像在高中math中一样在分数上完成(例如a/b * c/d = ac/bd )。

但是,当涉及pisqrtlogsin等等时,当然还是会遇到同样的麻烦。

TL; DR

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