为什么浮点数不准确?

时间:2014-02-20 00:39:02

标签: floating-point language-agnostic precision

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

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

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

这样一个看似简单的数字怎么能“太大”才能在 64位的内存中表达?

6 个答案:

答案 0 :(得分:201)

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

  

5179139571476070 * 2 -49

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

9.2可能只是92/10,但 10 不能表示为 2 n 如果 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']

解释数据

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

  • 登录
  • 指数
  • Mantissa(也称为有意义或分数)

登录

符号作为单个位存储在第一个组件中。这很容易解释:0表示浮点数为正数; 1表示它是否定的。由于9.2为正数,因此我们的符号值为0

指数

指数作为11位存储在中间组件中。在我们的案例中,0b10000000010。在十进制中,表示值1026。这个组件的一个怪癖是你必须减去一个等于 2 (#of bits) - 1 - 1 的数字来得到真正的指数;在我们的例子中,这意味着减去0b1111111111(十进制数1023)以获得真正的指数0b00000000011(十进制数3)。

尾数

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

  

6.0221413x10 23

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

  

1.0010011001100110011001100110011001100110011001100110

这不仅仅是一个简单的加法,因为存储在我们的第三个组件中的位实际上代表了尾数的小数部分,位于radix point的右侧。

处理十进制数时,我们"移动小数点"乘以或除以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 (不准确!)

在存储为浮点值后,乘以显示我们开始的数字(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 :(得分:25)

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

考虑分数2/3

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

  • 0.666 ...
  • 0.666
  • 0.667

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

数字基数

所以这里的数字基础是重要的。如果我们试图在基数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 < / p>

为什么浮点数不准确?

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

答案 2 :(得分:11)

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

无法准确表示无理数(例如π,sqrt(2)log(3)等)

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

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

但当涉及pisqrtlogsin等时,您仍会遇到同样的问题。

<强> TL; DR

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

答案 3 :(得分:1)

存在无限多个实数(以至于您无法枚举它们),并且存在无限多个有理数(可以枚举它们)。

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


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


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

答案 4 :(得分:0)

  

为什么我们不能用二进制浮点表示9.2?

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

如果分母的素数因子(当分数以其最低项表示时)是基数因子,则只能使用位置编号系统中的有限位数来精确表示分数。 / p>

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

另一方面,2的唯一素因子是2,所以在基数2中我们只能表示形式的分数a /(2 b

  

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

因为它是一种易于使用的简单格式,并且对于大多数用途而言足够准确。基本上与科学家使用&#34;科学记数法&#34;并在每一步将结果四舍五入到合理的位数。

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

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

  1. 如果您想要每个数字只有一个表示,那么在每次计算之后,您需要将分数减少到它的最低项。这意味着对于每个操作,您基本上都需要进行最大公约数计算。
  2. 如果在计算之后您最终得到了无法代表的结果,因为您需要找到最接近的可表示结果的分子或分母。这不是特权。
  3. 某些语言确实提供了分数类型,但通常它们与仲裁精度相结合,这避免了需要担心近似分数但是当数字经过大量计算时它会产生它自己的问题逐步调整分母的大小,因此分数所需的存储量可能会爆炸。

    有些语言也提供十进制浮点类型,这些类型主要用于以下情况:计算机获得的结果与预先存在的舍入规则相匹配(这主要是财务计算)。这些比二进制浮点更难以使用,但最大的问题是大多数计算机都没有为它们提供硬件支持。

答案 5 :(得分:-2)

尝试一下

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

'decimalValue'是您要转换的值。