邪恶的十进制/浮动中的邪恶

Ehs*_*ghi 16 python floating-point decimal python-2.4

我有大量的python代码试图处理4位小数精度的数字,并且由于很多原因我被困在python 2.4中.代码执行非常简单的数学(它是一个信用管理代码,主要采用或添加信用)

它混合了float和Decimal的使用(MySQLdb为SQL DECIMAL类型返回Decimal对象).在使用了几个奇怪的错误之后,我发现所有的根本原因是代码中的一些地方浮动和Decimals被比较.

我得到这样的案例:

>>> from decimal import Decimal
>>> max(Decimal('0.06'), 0.6)
Decimal("0.06")
Run Code Online (Sandbox Code Playgroud)

现在我担心的是我可能无法在代码中捕获所有这些情况.(一个普通的程序员会继续做x> 0而不是x> Decimal('0.0000')并且很难避免)

我想出了一个补丁(灵感来自python 2.7中对十进制包的改进).

import decimal
def _convert_other(other):
     """Convert other to Decimal.

     Verifies that it's ok to use in an implicit construction.
     """
     if isinstance(other, Decimal):
         return other
     if isinstance(other, (int, long)):
         return Decimal(other)
     # Our small patch begins
     if isinstance(other, float):
         return Decimal(str(other))
     # Our small patch ends
     return NotImplemented
decimal._convert_other = _convert_other
Run Code Online (Sandbox Code Playgroud)

我只是在一个非常早期的加载库中执行它,它将通过在比较之前允许float到Decimal转换来更改十进制包行为(以避免将python的默认对象命中对象).

我特意使用"str"而不是"repr",因为它修复了一些float的舍入情况.例如

>>> Decimal(str(0.6))
Decimal("0.6")
>>> Decimal(repr(0.6))
Decimal("0.59999999999999998")
Run Code Online (Sandbox Code Playgroud)

现在我的问题是:我在这里遗漏了什么?这相当安全吗?还是我在这里打破一些东西?(我认为该软件包的作者有很强的理由避免浮动这么多)

Ric*_*eur 4

我认为您想要开始raise NotImplementedError()而不是, 。return NotImplemented

你正在做的事情被称为“猴子修补”,只要你知道自己在做什么,意识到后果,并且能够接受这种后果,就可以做。通常,您将此限制为修复错误或其他一些您知道行为更改仍然正确且向后兼容的更改。

在这种情况下,因为您正在修补一个类,所以您可以在使用它的情况之外更改行为。如果另一个库使用十进制,并且以某种方式依赖于默认行为,则可能会导致微妙的错误。问题是,除非您审核所有代码(包括任何依赖项)并找到所有调用站点,否则您并不真正知道。

基本上 - 这样做的风险由您自己承担。

就我个人而言,我发现修复所有代码、添加测试并使其更难做错误的事情(例如,使用包装类或辅助函数)更让人放心。另一种方法是使用补丁来检测代码以找到所有调用站点,然后返回并修复它们。

编辑 - 我想我应该补充一点,他们避免浮动的可能原因是浮动不能准确地代表所有数字,如果您正在处理金钱,这一点很重要。

  • `return NotImplemented` 是正确的,并且是为不支持的比较返回的正确的[指定文档](http://docs.python.org/reference/datamodel.html#emulated-numeric-types)。它允许Python尝试寻找另一种方式来做事。 (10认同)