【问题标题】:Modulo operation on a python negative decimal.Decimal and a positive intpython负十进制的模运算。十进制和正整数
【发布时间】:2018-06-29 01:45:34
【问题描述】:

用简单的ints:

>>> -45 % 360
315

然而,使用decimal.Decimal

>>> from decimal import Decimal
>>> Decimal('-45') % 360
Decimal('-45')

我希望得到Decimal('315')

这有什么原因吗?有没有办法获得一致的行为(无需修补 decimal.Decimal)? (我没有改变上下文,也找不到如何改变它来解决这种情况)。

【问题讨论】:

  • 你听起来像是打补丁是极端的事情,应该避免,但是像 Python 这样的语言给你很大的空间在你自己的代码中做这件事(子类,神奇的“dunder”方法,包装器,等等。)。当然,您始终可以使用简单但有效的解决方法来编写自己的函数来使用,而不是使用内置语法。
  • @JohnY 对不起,我不是故意的;我的意图是让这个问题不被理解为“如何修补 Decimal 以获得这种行为?”,而是:“这种行为有原因吗?”和“是否有任何已经实施的方法来改变这种行为(例如通过上下文设置)?”。也许我应该提到我已经在需要的地方实施了一个小解决方法。

标签: python python-3.x decimal


【解决方案1】:

Python 的行为符合 IBM 的 General Decimal Arithmetic Specification

remainder 定义为:

remainder 接受两个操作数;它返回整数除法的余数。 […]

结果是按照divide-integer 所述计算整数除法运算后被除数的余数,如有必要,四舍五入为精确数字。结果的符号,如果非零,则与原被除数的符号相同。

所以因为Decimal('-45') // D('360')Decimal('-0'),所以余数只能是Decimal('-45')

为什么商是0而不是-1? specification 说:

除整数有两个操作数;它将两个数字相除并返回结果的整数部分。 […]

返回的结果定义为当被除数大于或等于除数时重复从被除数中减去除数的结果。在这个减法过程中,被除数和除数的绝对值都被使用:最终结果的符号与使用正常除法的结果相同。 […]

注释:[…]

  1. 定义了整数除法和余数运算,以便可以将它们计算为标准division 运算(如上所述)的副产品。一旦整数结果可用,除法过程就结束;股息的余数就是余数。

你能从 45 中减去多少次 360? 0次。整数结果可用吗?它是。然后商为 0 并带有一个减号,因为 divide 操作表明

结果的符号是操作数符号的异或

至于为什么十进制规范走这条路,而不是像数学中那样做余数总是正的,我推测这可能是为了减法算法的简单性.无需检查操作数的符号即可计算商的绝对值。无论如何,现代实现可能使用更复杂的算法,但在标准形成且硬件更简单(晶体管更少)的日子里,简单性可能是一个重要因素。有趣的事实:英特尔仅在 2007 年发布 Penryn 时才从 radix-2 整数除法转换为 radix-16。

【讨论】:

    【解决方案2】:

    经过长时间的搜索(因为搜索“%”、“mod”、“modulo”等会得到一千个结果),我终于发现,令人惊讶的是,this is intended

    Decimal 对象的算术运算之间存在一些小的差异 以及整数和浮点数的算术运算。当余数运算符 % 应用于 Decimal 对象,结果的符号是 除数而不是除数的符号:

    >>> (-7) % 4
    1
    >>> Decimal(-7) % Decimal(4)
    Decimal('-3')
    

    我不知道这样做的原因,但看起来不可能改变这种行为(不打补丁)。

    【讨论】:

    • Decimal 使用 math.fmod() 吗?他们都有相同的行为。 From the Python documentation:“因此,函数 fmod() 在处理浮点数时通常是首选,而 Python 的 x % y 在处理整数时是首选。”
    • @Piinthesky 根据我在库源代码中的理解,它确实是import math as _math,但_math 仅用于从float 构建Decimal 实例。 __mod__(以及其他方法,如__truediv__)计算对Decimal._divide() 的调用的商和余数,这依赖于对整数调用的divmod。然而它不会产生相同的结果......请注意:divmod(-45, 360) == (-1, 315)divmod(Decimal('-45'), Decimal('360')) == (Decimal('-0'), Decimal('-45'))
    • 我认为@Mr.T 通过提及fmod 走在了正确的轨道上。即使Decimal实现 根本没有触及fmod,其意图是模仿fmod行为 似乎至少是合理的。
    • 抱歉编辑。我以为添加摘录就足以说明问题,但后来我发现它有点复杂,所以我添加了自己的答案。
    猜你喜欢
    • 1970-01-01
    • 2012-08-17
    • 2019-04-29
    • 2014-11-30
    • 2017-04-21
    • 1970-01-01
    • 2019-06-16
    • 1970-01-01
    相关资源
    最近更新 更多