python 十进制比较

13 投票
3 回答
14127 浏览
提问于 2025-04-15 12:35

python 小数比较

>>> from decimal import Decimal
>>> Decimal('1.0') > 2.0
True

我原本以为可以正确转换 2.0,但在阅读了 PEP 327 后,我明白了不直接把浮点数转换成 Decimal 是有原因的。不过在这种情况下,难道不应该像其他情况一样抛出类型错误(TypeError)吗?

>>> Decimal('1.0') + 2.0
Traceback (most recent call last):
  File "<string>", line 1, in <string>
TypeError: unsupported operand type(s) for +: 'Decimal' and 'float'

其他运算符,比如 / - % // 等等,也是这样。

所以我想问的问题是:

  1. 这样做是对的吗?(在比较时不抛出异常)
  2. 如果我自己创建一个类,并写一个浮点数转换器,基本上就是 Decimal(repr(float_value)),这样做会有什么注意事项吗?我的使用场景只涉及价格的比较。

系统信息:在 Ubuntu 8.04.1 上运行 Python 2.5.2

3 个回答

1

“对不对”这个问题其实是见仁见智的,不过为什么没有自动转换的原因在PEP(Python增强提案)里有说明,这也是做出的决定。简单来说,浮点数(float)和十进制数(decimal)之间并不是总能完美转换的。所以,这种转换不应该是自动的。如果你在自己的应用中确定不会有足够的有效数字影响到你,那么创建一些可以让这种自动转换的类应该没问题。

另外,一个主要的观点是,现实中并没有太多这样的使用场景。如果你到处都用Decimal,可能会更简单。

3

大于比较之所以有效,是因为默认情况下,它适用于所有对象。

>>> 'abc' > 123
True

Decimal之所以正确,是因为它严格遵循了规范。至于这个规范是否是正确的方法,那是另一个问题。:)

处理浮点数时需要注意一些常见的陷阱,简单来说就是:要小心一些特殊情况,比如负零、正负无穷大和非数字(NaN),不要测试相等性(这和下一个要点有关),而且要记得数学计算可能会有些不准确。

>>> print (1.1 + 2.2 == 3.3)
False
26

关于第一个问题,这确实是我们设计的行为——对与错就不多说了(如果这让你的使用场景受影响了,抱歉,但我们是想做到通用!)。

具体来说,早在很久以前,每个Python对象都可以和其他对象进行不等比较——即使是那些实际上不应该比较的对象,也会被随意比较(在同一次运行中是一致的,但不同的运行可能不一样);主要的用例是对不同类型的列表进行排序,以便按类型对元素进行分组。

不过,复杂数被引入了一个例外,使得它们不能和任何东西进行比较——这还是很多年前的事,那时候我们偶尔会不太在意破坏用户的代码。现在我们在主要版本中对向后兼容性要求严格得多(比如在2.*系列和3.*系列中,尽管在2和3之间的兼容性是允许的——实际上,这就是3.*系列存在的意义,让我们可以以不兼容的方式修正过去的设计决策)。

这些随意的比较结果证明带来的麻烦大于好处,导致用户困惑;而按类型分组现在可以通过给sort函数传递一个key=lambda x: str(type(x))参数轻松实现;所以在Python 3中,不同类型的对象之间的比较,除非对象本身在比较方法中明确允许,否则会引发异常:

>>> decimal.Decimal('2.0') > 1.2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: Decimal() > float()

换句话说,在Python 3中,这种行为正如你所想的那样;但在Python 2中则不是(并且在任何Python 2.*中都不会如此)。

关于第二个问题,你会没事的——不过,可以看看gmpy,我希望这是一个有趣的方法,通过Farey树将双精度数转换为无限精度的分数。如果你处理的价格精确到分,使用'%.2f' % x而不是repr(x)

与其使用Decimal的子类,我会使用一个工厂函数,比如

def to_decimal(float_price):
    return decimal.Decimal('%.2f' % float_price)

因为一旦生成,得到的Decimal就是一个完全普通的Decimal。

撰写回答