在 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 部分 。
发表评论 取消回复