在 Python 中使用浮点数进行精确计算可能很危险。在这里,我们将解释原因并向您展示一种替代解决方案。

当你试图找出 2.32 x 3 是多少时,Python 会告诉你 6.959999999999999 。对于某些计算来说,这没问题。但是如果你正在计算涉及金钱的交易,这不是你想要看到的。当然,你可以将其四舍五入,但这有点不合时宜。

在本文中,我们将向您展示如何通过导入 decimal 模块来获得精确的计算结果。要了解这为何有用,我们首先需要介绍一些有关 Python 中数字表示方式的背景知识。

十进制数和二进制数

浮点数只是一个带有点的数字 decimal 内置 built-in 检查数字是否为浮点数

>>> type(1)

>>> type(1.0)

内置函数 float() int() 可以分别将其他数据类型(包括字符串)转换为浮点数和整数:

print(float(1))
	print(float('1.10'))
	print(int(1.1))

当你在 Python 中输入浮点数时,它以十进制(十进制)数字系统表示。以十进制 小数 为例 0.1234 ,它可以表示为 1/10 1 + 2/10 2 + 3/10 3 + 4/10 4 。但是,计算机硬件以二进制(二进制)存储数字。 二进制 小数 0.1011 (十进制等价于 0.6875 )可以表示为 1/2 1 + 0/2 2 + 1/2 3 + 1/2 4 .

问题是大多数十进制分数不能精确地表示为二进制分数。这意味着您输入到 Python 中的大多数浮点数只能用机器上存储的二进制浮点数来近似。

考虑十进制浮点数 0.1 。作为十进制小数,这可以完美地表示为 1/10 1 ,但最接近的二进制小数表示是 3602879701896397/2 55 。当您将其输入到 Python 终端时,该值会被四舍五入为 以 0.1 显示它,这可能会产生误导,因为存储在机器上的值实际上是: 0.1000000000000000055511151231257827021181583404541015625

使用 Python 来数钱

你可能开始意识到这可能会带来问题。考虑以下用 Python 数钱的场景:

unit_price = 2.32
number_sold = 3
money_received = 6.96
if unit_price * number_sold == money_received:
    print('Accounts balanced')
else:
    raise ValueError('Accounts not balanced')

这里, unit_price money_received 是浮点数,number_sold 是整数。在这个例子中,账簿确实是平衡的。但是,由于浮点数在您的机器上的表示和存储方式,这会产生错误,这表明收到的钱和销售金额之间存在差异。

这时 decimal 模块就派上用场了。它是 Python 默认安装的,有几个类。与我们的问题相关的是类 Decimal() 。使用此模块的优点是,十进制数可以在 Python 中精确表示。这种精确性也适用于算术结果。

因此,为了解决上述问题,可以按如下方式导入并使用此类:

from decimal import Decimal
unit_price = Decimal('2.32')
number_sold = 3
money_received = Decimal('6.96')
if unit_price * number_sold == money_received:
    print('Accounts balanced')
else:
    raise ValueError('Accounts not balanced')

请注意,此处的输入值 Decimal() 是字符串,但其他数据类型也可以用作输入。在上面的示例中,如果您将变量定义为 unit_price = Decimal(2.32) ,则最终会得到不精确的 2.32 表示,因为浮点数被转换为其二进制十进制等价数。

以浮点数 0.1 作为输入。输入 Decimal(0.1) Python 终端将返回 Decimal('0.1000000000000000055511151231257827021181583404541015625') 。我们之前遇到过这种表示。因此,要在 Python 中准确计算金额,最好使用字符串作为输入,这样更直观。

顺便说一句,如果你想学习如何在 Python 中使用字符串,请查看本 course .

变量 unit_price money_received 现在是 Decimal 对象,具有新的数据类型 decimal.Decimal 。使用内置函数自行检查, type() 就像我们上面所做的那样。

这些 Decimal 对象具有许多与其他数据类型(如浮点数和整数)相同的属性。可以进行常规数学运算,并且这些运算的结果具有精确的表示形式,这解决了平衡账簿的问题。与其他数据类型一样,Decimal 对象可用于 list 或字典,并且可以复制、打印、排序或转换为其他数据类型。

精确

对于许多涉及在 Python 中精确计算金钱的应用程序来说,能够设置数字的精度是件好事。该 decimal 模块为用户提供了这样做的能力,默认值为 28 位小数。另一方面,浮点数的精度通常只有不到 15 位。设置精度很简单:

from decimal import *
getcontext().prec = 10

该模块包含有效位概念,因此 的结果为 Decimal('1.10') + Decimal('20.20') Decimal('21.30') 尾随零保留以保持一致性。此外,该模块还包含定义数字舍入方式的功能。这也由 处理 getcontext() 。您可以在 官方文档 .

十进制对象和方法

Decimal 对象有多种方法可帮助进行进一步的计算、转换和检查。例如,有计算平方根、指数函数和对数的方法。您还可以找到最大值和最小值、使用逻辑运算以及检查有限值和 NaN 值等。

其中很多对于其他 Python packages ,例如 numpy(非常适合处理数组)、math(非常适合处理标量)或 pandas(非常适合数据 cleaning 和分析)。但使用 decimal 模块可以提供上述所有优势。

有助于处理格式化的两种方法是 normalize() quantize() 。前者删除最右边的尾随零并标准化对象的格式。例如, Decimal('1.10').normalize() Decimal('0.11e1').normalize() 都返回 Decimal('1.1') .

另一方面,后一个函数 quantize() 用于将值四舍五入为与参数的指数匹配,以便两个值具有相同的小数位数,而您无需事先知道有多少位小数。

>>> value1 = Decimal('1.01')
	>>> value2 = Decimal('10.001')
	>>> value1.quantize(value2)
Decimal('1.010')
	>>> value2.quantize(value1)
Decimal('10.00')

但需要注意的是: 仅量化计算的最终结果 ,以防止小的舍入误差累积。

使用 Python 来数钱:现在进展如何?

我们已经回顾了 Python 中如何使用 decimal 模块来计算金钱,但还有其他 Python 模块值得一提。例如, fractions 通过实现基于有理数的算术来支持精确计算。

Python 模块 money , prices 和 py-moneyed 为基于该 decimal 模块的资金管理提供了额外的功能。它们都支持处理不同的货币,因此您可以避免在不考虑汇率的情况下意外将欧元添加到美元。查看它们以了解哪一个最适合您的需求。考虑到金融已经变得如此自动化,拥有该 decimal 模块、 decimal.Decimal 数据类型和 Python 内置的附加功能对许多项目来说都是一个巨大的优势。

您还可以基于十进制功能创建自己的 project 来处理财务。对于有抱负的数据科学家来说,这门详细的 课程 ,它涉及我们在这里讨论的许多主题。

使用 Python 处理数字和进行精确计算固然很好,但大多数结果需要可视化才能正确理解。我们有一些很棒的数据可视化入门材料值得一看。请在此处查看 第 1 部分 和 第 2 部分 。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信小程序

微信扫一扫体验

立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部