打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
<font style="vertical-align: inherit;"><font style="vertical-align: inherit;">15.浮点算术:问题和限制</font></font>

15.浮点运算:问题和局限

浮点数在计算机硬件中表示为基数2(二进制)分数。例如,小数部分

0.125

值为1/10 + 2/100 + 5/1000,二进制分数相同

0.001

值为0/2 + 0/4 + 1/8。这两个分数具有相同的值,唯一真正的区别是第一个用基数10分数表示法写入,第二个用基数2表示。

不幸的是,大多数小数部分不能完全表示为二进制分数。结果是,通常,您输入的十进制浮点数仅由实际存储在机器中的二进制浮点数近似。

这个问题最初在基数10中更容易理解。考虑1/3的分数。您可以将其近似为基数10分数:

0.3

或更好,

0.33

或更好,

0.333

等等。无论你愿意记下多少位数,结果都不会完全是1/3,但将会越来越好地逼近1/3。

同样,无论您愿意使用多少个2位数,十进制值0.1都不能完全表示为基数2分数。在碱2中,1/10是无限重复的部分

0.0001100110011001100110011001100110011001100110011...

停在任何有限的位数,你得到一个近似值。在今天的大多数机器上,浮点数使用二进制分数近似,分子使用前53位从最高位开始,分母为2的幂。在1/10的情况下,二进制分数接近但不完全等于1/10的真实值。3602879701896397 / 2 ** 55

由于显示值的方式,许多用户不知道近似值。Python仅打印十进制近似值到机器存储的二进制近似值的真实十进制值。在大多数机器上,如果Python要打印存储为0.1的二进制近似值的真实十进制值,则必须显示

>>>
>>> 0.10.1000000000000000055511151231257827021181583404541015625

这比大多数人认为有用的数字更多,因此Python通过显示舍入值来保持可管理的位数

>>>
>>> 1 / 100.1

请记住,即使打印结果看起来像1/10的精确值,实际存储的值也是最接近的可表示二进制分数。

有趣的是,有许多不同的十进制数,它们共享相同的最接近的近似二进制分数。例如,数字0.10.100000000000000010.1000000000000000055511151231257827021181583404541015625都是近似的由于所有这些十进制值共享相同的近似值,因此可以在保留不变量的同时显示它们中的任何一个3602879701896397 / 2 ** 55eval(repr(x)) == x

从历史上看,Python提示和内置repr()函数将选择具有17位有效数字的函数0.10000000000000001从Python 3.1开始,Python(在大多数系统上)现在能够选择最短的并简单地显示0.1

请注意,这是二进制浮点的本质:这不是Python中的错误,也不是代码中的错误。您将在支持硬件浮点运算的所有语言中看到相同的类型(尽管某些语言默认情况下可能无法显示差异,或者在所有输出模式下都不会显示差异)。

为了获得更愉快的输出,您可能希望使用字符串格式来生成有限数量的有效数字:

>>>
>>> format(math.pi, '.12g')  # give 12 significant digits'3.14159265359'>>> format(math.pi, '.2f')   # give 2 digits after the point'3.14'>>> repr(math.pi)'3.141592653589793'

重要的是要意识到,从实际意义上说,这是一种错觉:你只是简单地围绕真实机器价值显示

一种错觉可能会产生另一种错觉。例如,由于0.1不完全是1/10,因此将三个0.1的值相加可能不会精确地产生0.3,或者:

>>>
>>> .1 + .1 + .1 == .3False

此外,由于0.1不能接近1/10的精确值,0.3不能接近3/10的精确值,那么使用round()函数预先舍入 不能帮助:

>>>
>>> round(.1, 1) + round(.1, 1) + round(.1, 1) == round(.3, 1)False

尽管数字不能接近其预期的精确值,但该round()函数可用于后舍入,以使具有不精确值的结果彼此相当:

>>>
>>> round(.1 + .1 + .1, 10) == round(.3, 10)True

二进制浮点运算有很多这样的惊喜。下面在“表示错误”部分中详细解释了“0.1”的问题。有关其他常见惊喜的更完整说明,请参阅浮点危险

正如接近结尾所说,“没有简单的答案。”不过,不要过分警惕浮点!Python浮点运算中的错误是从浮点硬件继承而来的,并且在大多数机器上每个操作的错误数量不超过1 ** 2 ** 53。这对于大多数任务来说已经足够了,但是你需要记住它不是十进制算术并且每次浮点运算都会遇到新的舍入错误。

虽然存在病态情况,但对于大多数临时使用浮点运算,如果您只是将最终结果的显示四舍五入到您期望的十进制数字,您将看到最终期望的结果。str()通常就足够了,并且为了更好的控制,请参阅格式字符串语法中str.format() 方法的格式说明符

对于需要精确十进制表示的用例,请尝试使用decimal适用于会计应用程序和高精度应用程序的实现十进制算术模块。

精确算术的另一种形式由fractions模块支持,该模块基于有理数实现算术(因此可以精确地表示像1/3这样的数字)。

如果您是浮点运算的重要用户,您应该查看Numerical Python软件包以及SciPy项目提供的数学和统计操作的许多其他软件包。请参阅< https://scipy.org >。

Python提供的工具,可对这些罕见的情况下帮助当你真的 想要知道一个浮动的精确值。float.as_integer_ratio()方法将float的值表示为分数:

>>>
>>> x = 3.14159>>> x.as_integer_ratio()(3537115888337719, 1125899906842624)

由于比率是精确的,它可以用于无损地重新创建原始值:

>>>
>>> x == 3537115888337719 / 1125899906842624True

float.hex()方法以十六进制表示浮点数(基数为16),再次给出计算机存储的精确值:

>>>
>>> x.hex()'0x1.921f9f01b866ep+1'

这个精确的十六进制表示可用于精确重建浮点值:

>>>
>>> x == float.fromhex('0x1.921f9f01b866ep+1')True

由于表示是精确的,因此可以跨不同版本的Python(平台独立性)可靠地移植值,并与支持相同格式的其他语言(例如Java和C99)交换数据。

另一个有用的工具是math.fsum()有助于减少求和过程中精度损失功能。当值添加到运行总计时,它会跟踪“丢失的数字”。这可能会对整体准确性产生影响,因此错误不会累积到影响最终总数的程度:

>>>
>>> sum([0.1] * 10) == 1.0False>>> math.fsum([0.1] * 10) == 1.0True

15.1。表示错误

本节详细介绍“0.1”示例,并说明如何自行对此类案例进行精确分析。假设基本熟悉二进制浮点表示。

表示错误是指某些(大多数,实际上)小数部分不能完全表示为二进制(基数2)分数的事实。这就是为什么Python(或Perl,C,C ++,Java,Fortran和许多其他人)通常不会显示您期望的确切十进制数的主要原因。

这是为什么?1/10不能完全表示为二进制分数。今天(2000年11月)几乎所有机器都使用IEEE-754浮点运算,几乎所有平台都将Python浮点数映射到IEEE-754“双精度”。754双精度包含53位精度,因此在输入时,计算机努力将0.1转换为J / 2 ** N形式的最接近的分数,其中J是包含正好53位的整数。重写

1 / 10 ~= J / (2**N)

J ~= 2**N / 10

并且回想起J恰好有53位(但是),N的最佳值是56:>= 2**52< 2**53

>>>
>>> 2**52 <=  2**56 // 10  < 2**53True

也就是说,56是N的唯一值,它使J正好为53位。然后,J的最佳可能值是舍入的商:

>>>
>>> q, r = divmod(2**56, 10)>>> r6

由于余数超过10的一半,因此通过四舍五入获得最佳近似值:

>>>
>>> q+17205759403792794

因此,754双精度的最佳可能近似值为1/10:

7205759403792794 / 2 ** 56

将分子和分母除以2将分数减少到:

3602879701896397 / 2 ** 55

请注意,由于我们向上舍入,这实际上比1/10大一点; 如果我们没有四舍五入,则商数将小于1/10。但在任何情况下都不能完全是 1/10!

所以计算机永远不会“看到”1/10:它看到的是上面给出的精确分数,它可以获得的最佳754双近似值:

>>>
>>> 0.1 * 2 ** 553602879701896397.0

如果我们将该分数乘以10 ** 55,我们可以看到55到十进制数字的值:

>>>
>>> 3602879701896397 * 10 ** 55 // 2 ** 551000000000000000055511151231257827021181583404541015625

意味着存储在计算机中的确切数字等于十进制值0.1000000000000000055511151231257827021181583404541015625。许多语言(包括旧版本的Python)不是显示完整的十进制值,而是将结果舍入为17位有效数字:

>>>
>>> format(0.1, '.17f')'0.10000000000000001'

fractionsdecimal模块进行这些计算很简单:

>>>
>>> from decimal import Decimal>>> from fractions import Fraction>>> Fraction.from_float(0.1)Fraction(3602879701896397, 36028797018963968)>>> (0.1).as_integer_ratio()(3602879701896397, 36028797018963968)>>> Decimal.from_float(0.1)Decimal('0.1000000000000000055511151231257827021181583404541015625')>>> format(Decimal.from_float(0.1), '.17')'0.10000000000000001'
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
你不知道的Python特性,其实可以解决很多问题!
深入浅出浮点数
c语言浮点数四舍五入的规则是什么
小数在计算机中的存储形式
Js 与浮点数
IEEE 754 浮点数的表示精度探讨
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服