如何在Python中准确地计算金钱


如何在Python中准确地计算金钱

在Python中使用浮点来进行精确计算可能是危险的。

当你试图找出 2.32 x 3 是多少时,Python 告诉你是 6.9599999999999。对于某些计算,这很好。但是如果你正在计算一个涉及金钱的交易,这不是你想看到的。

在这篇文章中,我们将向您展示如何通过导入小数模块来获得精确的计算结果。为了了解为什么这很有帮助,我们首先需要介绍一些关于数字在 Python 中如何表示的背景。

十进制和二进制数字

一个浮点数字只是一个带有小数点的数字。在Python中,你可以通过使用内置函数type()来检查一个数字是否是浮点数:

>>> type(1)

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

print(float(1))

print(float('1。 10'))

print(int(1.1))

当你在Python中输入一个浮点数时,它是以10进制(十进制)数制表示的。以十进制分数0.1234为例,它可以表示为1/101+2/102+3/103+4/104。然而,计算机硬件以基数2(二进制)存储数字。例如,二进制分数0.1011(十进制相当于0.6875)可以表示为1/21+0/22+1/23+1/24。

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

考虑一下十进制浮点数0.1。作为一个十进制小数,它可以完全用1/101来表示,但是最接近的二进制小数表示是3602879701896397/255。当你把这个值输入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 是一个整数。在这个例子中,账目确实是平衡的。然而,由于浮点数在你的机器上是如何表示和存储的,这产生了一个错误,表明收到的钱和销售的金额之间存在差异。

这就是小数模块的用武之地。它在默认的 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作为输入。在Python终端输入Decimal(0.1)会返回Decimal('0.1000000000000000055511151231257827021181583404541015625')。我们早些时候遇到过这种表示法。所以,要在 Python 中准确地计算金钱,使用字符串作为输入是更好、更直观的。

顺便说一下,如果你想学习如何在 Python 中使用字符串,请查看这个课程。

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

这些 Decimal 对象有许多与其他数据类型(如浮点和整数)相同的属性。正常的数学运算适用,而且这些运算的结果有一个精确的表示,这就是解决我们账目平衡问题的原因。像其它数据类型一样,小数对象可以在列表或字典中使用,它们可以被复制、打印、排序或转换为其它数据类型。

精度

对于许多涉及在 Python 中精确计算金钱的应用,能够设置数字的精度是很好的。十进制模块为用户提供了这样的能力,默认值为28位小数。另一方面,浮点数字的精度通常只有15位以下。设置精度很简单:

f from decimal import *

getcontext().prec = 10

该模块包括有效位的概念,所以Decimal('1.10') + Decimal('20.20')的结果是Decimal('21.30'),为了保持一致性,保留了尾部的零。此外,该模块还包括定义数字的四舍五入方式的能力。这也是通过getcontext()处理的。你可以在官方文档中阅读更多内容。

十进制对象和方法

十进制对象有几个方法,有助于进一步计算、转换和检查。例如,有一些方法可以计算平方根、指数函数和对数。你还可以找到最大值和最小值,使用逻辑运算,并对有限值和NaN值进行检查,等等。

很多这些对于其他Python包的用户来说是熟悉的,比如numpy(非常适合处理数组)、math(非常适合处理标量)或pandas(非常适合数据清理和分析)。但是使用十进制模块可以提供上面讨论的所有优点。

两个有助于处理格式化的方法是 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中数钱的情况,但是还有其他值得一提的Python模块。例如,fractions 通过实现基于有理数的算术来支持精确计算。

Python 模块 money、price 和 py-moneyed 在十进制模块的基础上为资金管理提供了额外的功能。它们都支持处理不同的货币,所以你可以避免不小心把欧元加到美元上而没有考虑到汇率。检查一下它们,看看哪一个最适合你的需要。鉴于金融已经变得如此自动化,拥有十进制模块,以及 decimal.Decimal 数据类型和 Python 内置的额外功能,对许多项目来说是一个巨大的优势。

你也可以在十进制功能的基础上创建自己的项目来处理你的财务。对于一个有抱负的数据科学家来说,这个详细的课程是一个很好的起点,它涉及到我们在这里讨论的许多主题。

在Python中处理数字和进行精确的计算是很好的,但大多数结果需要被可视化以正确理解它们。我们在数据可视化方面有一些很好的介绍性材料,值得一看。请看这里的第一部分和第二部分。


本文标签

热门标签

会员评论