Floating point 为什么浮点数不准确?

Floating point 为什么浮点数不准确?,floating-point,language-agnostic,precision,Floating Point,Language Agnostic,Precision,为什么有些数字在存储为浮点数时会失去准确性 例如,十进制数9.2可以精确表示为两个十进制整数92/10的比率,这两个整数都可以精确表示为二进制0b1011100/0b1010。但是,作为浮点数存储的相同比率永远不会完全等于9.2: 这样一个看似简单的数字怎么可能太大而无法在64位内存中表达呢?在大多数编程语言中,浮点数的表示方式非常类似:指数和尾数也被称为有效位。一个非常简单的数字,比如9.2,实际上是这个分数: 5179139571476070*2-49 其中指数为-49,尾数为5179139

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

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


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

在大多数编程语言中,浮点数的表示方式非常类似:指数和尾数也被称为有效位。一个非常简单的数字,比如9.2,实际上是这个分数:

5179139571476070*2-49

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

9.2可能只是92/10,但如果n限于整数值,则10不能表示为2n

查看数据 首先,使用一些函数来查看构成32位和64位浮点的组件。如果您只关心Python中的输出示例,请忽略以下内容:

def float_至_bin_零件编号,位=64: 如果位==32:单精度 int_pack='I' 浮动包装='f' 指数_位=8 尾数_位=23 指数偏差=127 elif位==64:双精度。所有python浮动都是这样的 int_pack='Q' 浮动包装='d' 指数_位=11 尾数_位=52 指数偏差=1023 其他: raise VALUERROR,“bits参数必须为32或64” bin_iter=iterbinstruct.uncompint_pack,struct.packfloat_pack,编号[0][2:]。rjustbits,“0” return[.joinislicepin\u iter,x表示1中的x,指数位,尾数位] 这个函数的背后有很多复杂的东西,解释起来很切题,但是如果你感兴趣的话,模块是我们的重要资源

Python的浮点是一个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的数字才能得到真正的指数;在我们的例子中,这意味着减去0B1111111十进制数1023得到真正的指数0b00000000011十进制数3

尾数 尾数作为52位存储在第三分量中。然而,这个组件也有一个怪癖。为了理解这个怪癖,在科学符号中考虑一个数字,例如:

6.0221413x1023

尾数是6.0221413。回想一下,科学记数法中的尾数总是以一个非零的数字开始。这同样适用于二进制,只是二进制只有两个数字:0和1。所以二进制尾数总是以1开头!当存储浮点时,二进制尾数前面的1被省略以节省空间;我们必须把它放回第三个元素的前面,才能得到真正的尾数:

1.0010011001100110

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

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

0.0010011001100110


在十进制记数法中,这与将675539944105574除以4503599627370496得到0.14999999999是一样的。这是一个比率的例子,可以精确地用二进制表示,但只能近似地用十进制表示;有关详细信息,请参阅:

现在我们已经将第三个分量转换成了一个小数,加上1就得到了真正的尾数

重述组件 为第一个组件签名:0表示正,1表示负 指数中间分量:减去2位-1-1得到真正的指数 尾数最后一部分:除以2位a nd加1得到真正的尾数 计算数字 将这三部分放在一起,我们得到了一个二进制数:

1.0010011001100110 x 1011

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

1.149999999999999 x 23不准确

然后乘以以显示我们从9.2开始的数字在存储为浮点值后的最终表示形式:

9.199999999993

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

1.0010011001100110 x 1011

将尾数转换为整数:

1001100110 x 1011-110100

转换为十进制:

5179139571476070 x 23-52

减去指数:

5179139571476070 x 2-49

将负指数化为除法:

5179139571476070/249

乘指数:

5179139571476070/562949953421312

这等于:

9.199999999993

9.5 你已经可以看到尾数只有4位数,后面跟着很多零。但是让我们来看看这些步骤

组装二进制科学符号:

1.0011 x 1011

将小数点移动:

10011 x 1011-100

减去指数:

10011 x 10-1

二进制到十进制:

19 x 2-1

除法负指数:

19/21

乘指数:

19/2

等于:

9.5

进一步阅读 浮点gui.de 戈德伯格1991 维基百科 docs.python.org 这不是一个完整的答案,已经涵盖了很多好的方面,我不会重复,但我想强调的是,数字的表示在多大程度上取决于您所处的基础

考虑分数2/3 在good ol'base 10中,我们通常将其写为

0.666... 0.666 0.667 当我们观察这些表示时,我们倾向于将它们与分数2/3联系起来,即使只有第一个表示在数学上等于分数。第二个和第三个表示/近似值的误差约为0.001,这实际上比9.2和9.199999993之间的误差要严重得多。事实上,第二个表示法甚至没有正确舍入!尽管如此,我们对0.666作为数字2/3的近似值没有问题,因此在大多数程序中,9.2的近似值应该没有问题。是的,在一些节目中,这很重要

基数 这就是基数的关键所在。如果我们试图在基数3中代表2/3,那么

2/310=0.23

换言之,我们有一个精确的,有限的表示相同的数字通过切换基地!值得注意的是,即使你可以将任意数转换成任意基,所有有理数在某些基中都有精确的有限表示,而在其他基中则没有

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

1/210=0.510=0.12=0.1111…3

为什么浮点数不准确?
因为通常情况下,它们是在逼近无法在基数2中有限表示的有理数,数字重复,通常它们是在逼近可能是无理的实数,而这些实数可能无法在任何基数中有限多个数字中表示。

尽管所有其他答案都是好的,但仍然缺少一点:

不可能精确地表示无理数,例如π、sqrt2、log3等

这就是为什么他们被称为非理性。世界上再多的比特存储空间也不足以容纳其中的一个。只有符号算术能够保持其精度

虽然如果你想把数学需求限制在有理数上,但精度问题就变得容易处理了。您需要存储一对可能非常大的整数a和b,以保存分数a/b表示的数字。你所有的算术都必须在分数上完成,就像高中数学一样,例如a/b*c/d=ac/bd

但当然,当涉及到pi、sqrt、log、sin等时,您仍然会遇到同样的麻烦

TL;博士

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

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

浮点数稍微简化了位置编号系统,该系统具有有限的位数和可移动的基数

如果分数的素数因子 分数以最低项表示时的分母是基数的因子

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

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

计算机为什么使用这种表示法

因为它是一种简单的格式,在大多数情况下都足够精确。基本上,这与科学家使用科学记数法并在每一步将结果四舍五入到合理的位数的原因相同

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

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

如果您希望每个数字都有一个表示形式,那么在每次计算之后,您需要将分数减少到其最低项。这意味着对于每个操作,基本上都需要进行最大公约数计算。 如果在计算之后,由于分子或分母需要找到最接近的可表示结果,结果无法表示。这不是三部曲。 有些语言确实提供分数类型,但通常它们与任意精度结合使用,这避免了需要担心近似分数的问题,但这会产生自己的问题,当一个数字经过大量计算步骤时,分母的大小以及分数所需的存储可能会爆炸


有些语言还提供十进制浮点类型,这些类型主要用于计算机获得的结果必须与预先存在的舍入规则相匹配的情况,这些舍入规则主要是在金融计算中人为编写的。与二进制浮点数相比,使用它们稍微困难一些,但最大的问题是大多数计算机都不提供硬件支持。

有无限多的实数,你无法枚举它们,并且有无限多的有理数可以枚举它们

浮点表示法是一种有限表示法,就像计算机中的任何东西一样,因此不可避免地有许多数字无法表示。特别是,64位仅允许您区分18446744073709551616个不同的值,与无穷大相比,这算不了什么。根据标准惯例,9.2不是其中之一。对于某些整数m和e,它们的形式为m.2^e

你可能会想出一个不同的计算系统,比如10,其中9.2会有一个精确的表示。但其他数字,比如1/3,仍然无法表示


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

还有一个例子说明了如何用另一种方法——给定一个数字的十进制表示,如何构造浮点等价物。长除法非常清楚地显示了在尝试表示数字后如何得到余数。如果您想真正规范您的答案,应该添加。如果您谈论的是Python和浮点,我建议至少在链接中包含Python教程:对于Python程序员来说,这应该是浮点问题的一站式参考资料。如果在某些方面有所欠缺,并且几乎可以肯定,请确实在Python bug tracker上打开一个问题,以获取更新或更改。@mhlester如果这变成了社区wiki,请随时将我的答案合并到您的答案中。此答案肯定还应链接到,因为它可能是对初学者最好的介绍。在我看来,它甚至应该超越每一位计算机科学家应该知道的如今,能够合理理解戈德堡论文的人通常已经很清楚了。这是一个比率的例子,可以精确地用二进制表示,但只能近似地用十进制表示。事实并非如此。所有这些“二次幂上的数字”比率都是十进制的。任何近似值都只是为了方便而缩短十进制数。换句话说,基数-3适合1/3,就像基数-10适合1/10一样。两个分数都不适用于基数-2@mhlester对一般来说,对于分母为N或其倍数的任何分数,base-N都是完美的
cal工具箱跟踪什么被什么分割,并且在这个过程中可以保持所有有理数的无限精度。就像物理学家喜欢把他们的方程符号化到最后一刻,以防π等因子抵消。@Floris我也见过这样的情况,一种算法只执行基本的算术运算,即保留输入的合理性,确定输入是否可能是合理的,使用普通的浮点运算执行数学运算,然后在最后重新估计有理近似值,以修复任何舍入误差。尤其是Matlab的算法做到了这一点,它极大地帮助了数值稳定性。@SchighSchagh-有趣的是,我不知道这一点。我确实知道,在双精度的今天,数值稳定性并没有得到足够的教育。这意味着许多人错过了学习许多漂亮算法的优雅之处。我真的很喜欢计算和纠正自己错误的算法。有趣的是,无理基确实存在,例如,无理数只能在其基中表示。例如,pi是10,在基本piPoint中仍然有效:无论系统如何,某些数字永远无法表示。通过更改基数,您不会获得任何收益,因为这样其他数字将无法再表示。请参阅
>>> float_to_bin_parts(9.2)
['0', '10000000010', '0010011001100110011001100110011001100110011001100110']
>>> float_to_bin_parts(9.5)
['0', '10000000010', '0011000000000000000000000000000000000000000000000000']