为什么浮点数不准确?

mhl*_*ter 181 language-agnostic floating-point precision

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

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

32-bit "single precision" float: 9.19999980926513671875
64-bit "double precision" float: 9.199999999999999289457264239899814128875732421875
Run Code Online (Sandbox Code Playgroud)

这样一个看似简单的数字如何在64位内存中表达"太大" ?

mhl*_*ter 219

在大多数编程语言中,浮点数的表示方式与科学记数法很相似:带有指数和尾数(也称为有效数字).比如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)]
Run Code Online (Sandbox Code Playgroud)

这个函数背后有很多复杂性,它解释起来很简单,但是如果你感兴趣的话,我们目的的重要资源就是struct模块.

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

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

>>> float_to_bin_parts(9.2)
['0', '10000000010', '0010011001100110011001100110011001100110011001100110']
Run Code Online (Sandbox Code Playgroud)

解释数据

你会看到我把返回值分成三个部分.这些组件是:

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

标志

符号作为单个位存储在第一个组件中.这很容易解释: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.0010011001100110011001100110011001100110011001100110

这不仅仅是一个简单的加法,因为存储在我们的第三个分量中的比特实际上代表了尾数的小数部分,即基数点的右边.

当处理十进制数时,我们通过乘以或除以10的幂来"移动小数点".在二进制中,我们可以通过乘以或除以2的幂来做同样的事情.由于我们的第三个元素有52位,我们除以通过2 52将它向右移动52个位置:

0.0010011001100110011001100110011001100110011001100110

在十进制,这是同分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']
Run Code Online (Sandbox Code Playgroud)

您已经可以看到尾数只有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



进一步阅读

  • 这个答案肯定也应该链接到[floating-point-gui.de](http://floating-point-gui.de/),因为它可能是初学者最好的介绍.国际海事组织,甚至应该超越"每个计算机科学家都应该知道的......" - 这些天,能够合理理解戈德堡论文的人通常已经很清楚这一点. (4认同)
  • “这是一个可以用二进制精确表示但只能近似用十进制表示的比率的示例”。这不是真的。所有这些“二的幂数”比率都是精确的十进制。任何近似值都只是为了方便起见而缩短小数。 (3认同)
  • 还有一个 [不错的教程](http://kipirvine.com/asm/workbook/floating_tut.htm) 展示了如何采取另一种方式 - 给定数字的十进制表示形式,如何构造浮点等效值。“长除法”方法非常清楚地显示了在尝试表示数字后如何得到“余数”。如果您想使您的答案真正“规范”,则应该添加。 (2认同)

Nic*_*rca 26

这不是一个完整的答案(mhlester已经涵盖了很多我不会复制的好地方),但我想强调一个数字的表示取决于你工作的基础.

考虑分数2/3

在良好的基础10中,我们通常会将其写成类似的东西

  • 0.666 ...
  • 0.666
  • 0.667

当我们查看这些表示时,我们倾向于将它们中的每一个与分数2/3相关联,即使只有第一个表示在数学上等于分数.第二和第三表示/近似的误差大约为0.001,实际上比9.2和9.1999999999999993之间的误差差.事实上,第二个表示甚至没有正确舍入!然而,我们没有将0.666作为数字2/3的近似值的问题,所以我们不应该在大多数程序中如何逼近9.2.(是的,在某些程序中它很重要.)

数字基数

所以这里的数字基础是重要的.如果我们试图在基数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中无法有限地表示的有理数(数字重复),并且通常它们近似于在任何基数中可能无法在有限多个数字中表示的实数(可能是无理数)数.

  • 所以换句话说,**base-3**对于`1/3'来说是完美的,就像**base-10**对于`1/10'来说是完美的.这两个部分都不适用于**base-2** (3认同)
  • @Floris我也看到了一种算法,它只执行基本算术(即保留输入的合理性),确定输入是否(可能)是合理的,使用正常浮点算法执行数学运算,然后重新估计一个理性最后的近似值来修复任何舍入误差.特别是Matlab的[减行行梯形式](http://www.mathworks.com/help/matlab/ref/rref.html)算法做到了这一点,它极大地帮助了数值稳定性. (3认同)
  • @mhlester是的.通常,**base-N**适用于分母为"N"或其倍数的任何分数. (2认同)
  • 这就是为什么一些数值工具箱跟踪"什么被什么划分"的原因之一,并且在此过程中可以保持所有有理数的"无限精度".正如物理学家喜欢将他们的方程符号保持到最后可能的时刻,以防"π"等因素抵消. (2认同)

Lum*_*mpN 12

虽然所有其他答案都很好,但仍有一件事缺失:

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

这就是他们被称为非理性的原因.世界上没有多少比特存储就足以容纳其中一个.只有符号算术才能保持其精度.

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

当然,你仍然会遇到同样的麻烦时pi,sqrt,log,sin,等都有涉及.

TL; DR

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

  • 无理数可以(仅)以其基数表示.例如,pi是基本pi中的10 (4认同)
  • 点仍然有效:_无论系统如何,有些数字都无法表示._您不会通过更改基数来获得任何数据,因为之后其他一些数字将无法再表示. (4认同)
  • 有趣的是,确实存在非理性基础.[Phinary](http://en.wikipedia.org/wiki/Golden_ratio_base),例如. (3认同)

Yve*_*ust 8

有无限多个实数(多到你无法一一列举),也有无限多个有理数(可以一一列举)。

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


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


另请注意,双精度浮点数非常准确。它们可以用多达 15 位精确数字来表示非常广泛的范围内的任何数字。对于日常生活计算,4 或 5 位数字就足够了。你永远不会真正需要那 15 个,除非你想计算你生命中的每一毫秒。