在 Python 中模数(%)运算符如何处理负数?

2024-12-12 08:40:00
admin
原创
99
摘要:问题描述:在 Python 中该运算符究竟是如何%工作的,特别是涉及负数时?例如,为什么-5 % 4计算结果为3,而不是-1?解决方案 1:与 C 或 C++ 不同,Python 的取模运算符 ( %) 总是返回与分母(除数)符号相同的数字。您的表达式得出 3,因为(-5)/ 4 = -1.25 -->...

问题描述:

在 Python 中该运算符究竟是如何%工作的,特别是涉及负数时?

例如,为什么-5 % 4计算结果为3,而不是-1


解决方案 1:

与 C 或 C++ 不同,Python 的取模运算符 ( %) 总是返回与分母(除数)符号相同的数字。您的表达式得出 3,因为

(-5)/ 4 = -1.25 --> floor(-1.25) = -2

(-5) % 4 = (-2 × 4 + 3) % 4 = 3。

选择它而不是 C 行为是因为非负结果通常更有用。一个例子是计算星期几。如果今天是星期二(第 2 天),那么N天前的星期几是什么?在 Python 中,我们可以使用

return (2 - N) % 7

但在 C 语言中,如果N ≥ 3,我们会得到一个负数,这是一个无效数,我们需要通过添加 7 来手动修复它:

int result = (2 - N) % 7;
return result < 0 ? result + 7 : result;

(请参阅http://en.wikipedia.org/wiki/Modulo_operator了解如何确定不同语言的结果符号。)

解决方案 2:

以下是 Guido van Rossum 的解释:

http://python-history.blogspot.com/2010/08/why-pythons-integer-division-floors.html

本质上,a/b = q 且余数 r 保留关系 b*q + r = a 和 0 <= r < b。

解决方案 3:

其他答案,尤其是所选答案,已经很好地回答了这个问题。但我想介绍一种可能更容易理解的图形方法,以及在 Python 中执行正常数学模数的 Python 代码。

Python 模数入门指南

模函数是一个方向函数,它描述了在对无限数的 X 轴进行除法时,我们必须向前或向后移动多少。假设你正在做7%3

在此处输入图片描述

因此,从正向来看,您的答案将是 +1,但从反向来看,

在此处输入图片描述

你的答案是 -2。从数学上来说,这两个都是正确的。

类似地,负数也会有 2 个模数。例如:-7%3,结果可以是 -1 或 +2,如下所示 -

在此处输入图片描述

前进方向


在此处输入图片描述

后向


在数学中,我们选择向内跳跃,即正数向前跳跃,负数向后跳跃。

但在 Python 中,所有正数模运算都有正向。因此,你的困惑是——

>>> -5 % 4 
3

>>> 5 % 4
1

以下是 Python 中内向跳跃类型模数的 Python 代码:

def newMod(a,b):
    res = a%b
    return res if not res else res-b if a<0 else res

这将给出 -

>>> newMod(-5,4)
-1

>>> newMod(5,4)
1

很多人会反对内跳法,但我个人认为这个比较好!!

解决方案 4:

python中,模运算符的工作方式如下。

>>> mod = n - math.floor(n/base) * base

因此结果是(就你的情况而言):

mod = -5 - floor(-1.25) * 4
mod = -5 - (-2*4)
mod = 3

而其他语言(例如C、JAVA、JavaScript)则使用截断而不是向下取整。

>>> mod = n - int(n/base) * base

结果是:

mod = -5 - int(-1.25) * 4
mod = -5 - (-1*4)
mod = -1

如果您需要有关 Python 中舍入的更多信息,请阅读此文。

解决方案 5:

正如指出的那样,Python 模数对其他语言的惯例做出了合理的例外。

这使得负数具有无缝的行为,特别是与//整数除法运算符结合使用时,因为%模数通常是这样的(如 math.divmod ):

for n in range(-8,8):
    print n, n//4, n%4

生成:

 -8 -2 0
 -7 -2 1
 -6 -2 2
 -5 -2 3

 -4 -1 0
 -3 -1 1
 -2 -1 2
 -1 -1 3

  0  0 0
  1  0 1
  2  0 2
  3  0 3

  4  1 0
  5  1 1
  6  1 2
  7  1 3
  • Python%始终输出零或正数*

  • Python//总是向负无穷方向舍入

  • ...只要右操作数为正。另一方面11 % -10 == -9

解决方案 6:

处理整数除法和负数取模没有最好的方法。如果a/b的大小和 的符号相同,那就太好了。如果确实是模数 b,(-a)/b那就太好了。由于我们真正想要,因此前两个是不兼容的。a % b`a == (a/b)*b + a%b`

保留哪一个是一个难题,双方都有争论。C 和 C++ 将整数除法四舍五入为零(所以a/b == -((-a)/b)),而 Python 显然不会这样做。

解决方案 7:

您可以使用:

result = numpy.fmod(x,y)

它将保留符号,请参阅numpy fmod() 文档。

解决方案 8:

这就是模数的用途。如果你对一系列数字进行模数运算,它将给出一个值循环,例如:

ans = num % 3
數量
30
22
11
00
-12
-21
-30

解决方案 9:

值得一提的是,python 中的除法与 C 中的除法不同:考虑

>>> x = -10
>>> y = 37

在 C 中你期望结果

0

python 中的 x/y 是什么?

>>> print x/y
-1

% 是模数 - 不是余数!而 C 中的 x%y 结果是

-10

python 产生。

>>> print x%y
27

你可以像在 C 中一样获得两者

部门:

>>> from math import trunc
>>> d = trunc(float(x)/y)
>>> print d
0

得出余数(使用上面的除法):

>>> r = x - d*y
>>> print r
-10

这种计算可能不是最快的,但它可以对 x 和 y 的任意符号组合进行操作,以实现与 C 中相同的结果,而且它避免了条件语句。

解决方案 10:

我也认为这是 Python 的一个奇怪行为。结果发现,我没有很好地解决除法问题(在纸面上);我给商赋值为 0,给余数赋值为 -5。糟糕……我忘记了整数的几何表示。通过回忆数轴给出的整数几何,可以得到商和余数的正确值,并检查 Python 的行为是否正常。(虽然我认为你很久以前就已经解决了你的担忧)。

解决方案 11:

@Deekshant 使用可视化很好地解释了这一点。理解 %(modulo) 的另一种方法是提出一个简单的问题。

在 X 轴上,最接近被除数且能被除数整除的较小数字是什么?

让我们看几个例子。

5 % 3 

5 是被除数,3 是除数。如果你问上面的问题,3 是能被除数整除的最接近的最小数。答案是 5 - 3 = 2。对于正被除数,最接近的最小数总是被除数的右侧。

-5 % 3

能被 3 整除的最接近的最小数是 -6,所以答案是 -5 - (-6) = 1

-5 %4

能被 4 整除的最接近的最小数是 -8,所以答案是 -5 - (-8) = 3

Python 用这种方法回答每一个模数表达式。希望你接下来能理解表达式将如何执行。

解决方案 12:

我试图写一个涵盖所有输入情况的一般答案,因为许多人会询问各种特殊情况(不仅仅是 OP 中的情况,而且还特别询问右侧的负值而且这实际上都是同一个问题

Python 实际上给我们提供了什么a % b,用文字来解释一下?

假设abfloat和/或int值,有限的(不是math.infmath.nan等等)并且b不为零......

结果c具有 符号的唯一数字,使得能被和整除。当和均为,它将为,而当 或 为 int 时,它将(即使它恰好等于整数) 。ba - c`babs(c) < abs(b)intab intfloat` `ab`

例如:

>>> -9 % -5
-4
>>> 9 % 5
4
>>> -9 % 5
1
>>> 9 % -5
-1

符号保存也适用于浮点数;即使a能被整除b,也有可能得到不同的0.0-0.0结果(回想一下浮点数中零是有符号的),并且符号将匹配b

概念证明:

import math

def sign(x):
    return math.copysign(1, x)

def test(a: [int, float], b: [int, float]):
    c = a % b
    if isinstance(a, int) and isinstance(b, int):
        assert isinstance(c, int)
        assert c * b >= 0 # same sign or c == 0
    else:
        assert isinstance(c, float)
        assert sign(c) == sign(b)
    assert abs(c) < abs(b)
    assert math.isclose((a - c) / b, round((a - c) / b))

用一种涵盖所有可能的符号和类型组合并考虑浮点不精确性的方式来表达这一点有点困难,但我对上述内容很有信心。s 的一个具体问题float是,由于浮点不精确性, 的结果a % b 有时似乎给出b而不是0。事实上,它只是给出了一个非常接近 的值b,因为除法的结果并不十分精确:

>>> # On my version of Python
>>> 3.5 % 0.1
0.09999999999999981
>>> # On some other versions, it might appear as 0.1,
>>> # because of the internal rules for formatting floats for display

如果什么abs(a) < abs(b)

很多人似乎认为这是一个特殊情况,或者由于某种原因难以理解发生了什么。但这并没有什么特别的。

例如:考虑。 作为一个正数(因为是正数),我们需要从 中减去-1 % 3多少才能得到能被 整除的结果?不能被 整除;是,也不能被 整除;但是,可以被 整除(正好是 的倍数)。 通过减去,我们回到可整除性;因此是我们预测的答案——结果如下:3`-13-13-1 - 1-2-1 - 2-33-12`2

>>> -1 % 3
2

b那么等于零怎么样?

raise ZeroDivisionError无论是b整数零、浮点正零还是浮点负零,结果都是 。特别是,它不会导致 NaN 值。

那么特殊浮点值呢?

正如人们所料,只要不为零(覆盖其他所有内容), 的nan有符号无穷大值就会a产生结果。的值也会导致。 NaN 不能带符号,因此在这些情况下 的符号无关紧要。nan`bnanbnanb`

也可以预料,无论符号如何,inf % inf都会给出nan。如果我们将无限数量的 s 分配a给无限数量的bs ,则无法说出“哪个无穷大更大”或大多少。

唯一稍微令人困惑的情况是当b是有符号无穷大值时:

>>> 0 % inf
0.0
>>> 0 % -inf
-0.0
>>> 1 % inf
1.0
>>> 1 % -inf
-inf

与往常一样,结果采用 的符号b0可以被任何东西(NaN 除外)整除,包括无穷大。但没有其他任何东西可以整除无穷大。如果a具有与 相同的符号b,则结果就是a(作为浮点值);如果符号不同,则结果为b。为什么?好吧,考虑-1 % inf。没有一个有限的值可以从 中减去-1,以得到0(我们可以除以无穷大的唯一值)。所以我们必须继续下去,直到无穷大。同样的逻辑适用于1 % -inf,但所有符号都反转。

那么其他类型呢?

这取决于类型。例如,Decimal 类型重载了运算符,因此结果采用分子的符号,即使它在功能上表示与 a 相同类型的值float。当然,字符串将其用于完全不同的东西。

为什么不总是给出积极的结果,或者采取的符号a

这种行为是由整数除法引起的。虽然%它恰好适用于浮点数,但它是专门为处理整数输入而设计的,floats 的结果与此一致。

在选择给出a // b向下取整的除法结果之后,该%行为保留了一个有用的不变量:

>>> def check_consistency(a, b):
...     assert (a // b) * b + (a % b) == a
... 
>>> for a in range(-10, 11):
...     for b in range(-10, 11):
...         if b != 0:
...             check_consistency(a, b) # no assertion is raised
...

换句话说:将模数值加回去,纠正整数除法产生的错误

(当然,这让我们回到第一部分,并说a % b只是计算a - ((a // b) * b)。但这只是把问题抛在一边;我们仍然需要解释//有符号值的作用,尤其是浮点数。)

一个实际应用是将像素坐标转换为图块坐标时。//告诉我们哪个图块包含像素坐标,然后%告诉我们该图块内的偏移量。假设我们有 16x16 个图块:那么 x 坐标为 的图块0包含 x 坐标0..15(包括 x 坐标)的像素,图块1对应于像素坐标值16..31,等等。如果像素坐标为 ,100我们可以轻松计算出它在图块 中100 // 16 == 6,并100 % 16 == 4从该图块的左边缘偏移像素。

我们无需更改任何内容即可处理原点另一侧的图块。坐标处的图块-1需要考虑左侧的下一个 16 个像素坐标0(包括 ie)-16..-1。事实上,我们发现 eg -13 // 16 == -1(因此坐标位于该图块中),以及-13 % 16 == 3(这就是它与图块左边缘的距离)。

通过将图块宽度设置为正数,我们定义了图块内的坐标从左到右移动。因此,知道某个点位于特定图块内,我们总是希望该偏移计算的结果为正数。Python 的%运算符在 y 轴的两侧都给出了正数。

如果我想让它以另一种方式工作该怎么办?

math.fmod将采用分子的符号。它还将返回浮点结果,即使对于两个整数输入也是如此,并且对于a具有非 nanb值的有符号无穷大值会引发异常:

>>> math.fmod(-13, 16)
-13.0
>>> math.fmod(13, -16)
13.0
>>> math.fmod(1, -inf) # not -inf
1.0
>>> math.fmod(inf, 1.0) # not nan
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: math domain error

否则,它会以相同的方式处理特殊情况 - 零值会b引发异常;否则任何nan存在都会导致nan结果。

如果这也不符合您的需要,那么请仔细定义每个可能的特殊情况的确切期望行为,找出它们与内置选项的不同之处,并制作一个包装函数。

解决方案 13:

我只浏览了其他答案,但我没有看到这一点显而易见:如果您有一个数组并且希望索引-1引用最后一个元素,Python 的 modulo 实现可以正确实现它,但 Java 等却不能。因此,要确保数组索引i(可以为负数)在范围内(大小n),在 Python 中就足够了,但您必须用 Java 等i % n编写。(((i % n) + n) % n)

恕我直言,这是一个极其常见的用例。

您可能知道,Python 已经为数组实现了此索引约定:

>>> a = [3, 5, 7]
>>> a[-1]
7

然而,不仅 Java 不需要,而且将其“修复”到 Python 约定还需要上面提到的更复杂的公式。

是的,人们可以争辩说 - 相反 - 这种约定可能会掩盖一些编程错误,而你并不是真的想让你的索引像那样环绕。


顺便说一句,python 实际上也为浮点实现了这一点。

>>> -.25 % 1
0.75

FP 扩展与它在 Csound 中用于生成相量 (mod 1) 的方式一致。相量也可以“从高到低”扫描,通过沿与“通常”正频率相反的方向扫描波表来产生负频率。


顺便说一句,Python 的 def 可能存在的一个问题是负除数:

>>> -.25 % -1
-0.25

在 Kevin 的回答中链接的 van Rossum 的文章中,他似乎并没有过多讨论这个角度的原理。他只是说:“顺便说一句,对于负 [除数] b,一切都会翻转,不变量变为:0 >= r > b。”然而,有些人(例如 Boute)对此表示异议,并认为正确的方法是始终有一个正的余数/模数r,按照欧几里得。不过可以说,负“大小”比负索引少见,所以这种情况出现得较少。

不管怎样,如果你对这个角度感到好奇:

欧几里得定义 [余数永远为正 - Python 不将其用于负除数,但用于正除数] 与代数中的定义一致,该定义可推广到整数以外的欧几里得环(例如多项式)。

当将定义扩展到多项式时,不等式就多项式的度数而言 deg( r ( x ))<deg( b ( x ))。一般来说,粗略地说,某个范数。这就是为什么对于整数,对于“正确”的欧几里得定义,| r |<| b |。从技术上讲,这意味着整数中的每个除数都有两个有效商,因为对于基于范数的 |r|<|b| 不等式,符号并不重要。而在其他一些环中,可能会有更多的商,例如圣安德鲁斯大学的讲座页面讨论了一个“2D”环示例,其中有最多 4 个可能的商符合基于范数的定义。很可能,Boute 可能误解了这个细节。(或者我误解了他在上面的引文中所说的话。)

无论如何,那篇(Boute)论文认为 C/Java 风格的模定义是迄今为止最糟糕的选择,原因有很多,比如即使对于固定除数也缺乏周期性等等。而他在这一点上似乎并没有错。

Boute 的选择(他所谓的欧几里得——比数学家对欧几里得的定义更严格)实际上有一个优势,但它与右移位运算与整数除以负被除数的兼容性有关,并在另一篇论文[Leijen 的论文] 的摘要中得到了更好的解释。即:

>>> -1 // -2
0
>>> - (-1 >> 1)
1
>>> - (-1 // 2)
1

因此,Boute 的观点(Leijen 重申)是,如果将负被除数的除法定义为后者(因此,例如-1 // -2 = 1,您就被迫选择一个非负余数,即使对于负被除数也是如此。我猜 van Rossum 认为这并不重要。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1043  
  IPD(Integrated Product Development,集成产品开发)是一种系统化的产品开发方法论,旨在通过跨职能团队的协作,优化产品开发的效率和质量。IPD流程强调从市场需求出发,通过并行工程、跨部门协作和阶段性评审,确保产品从概念到上市的每个环节都高效且可控。随着敏捷开发方法的普及,越来越多的企业开始...
华为IPD流程   41  
  随着企业产品开发复杂度的提升以及市场需求的快速变化,传统的产品开发模式逐渐显现出局限性。集成产品开发(IPD)流程与敏捷开发(Agile Development)作为两种主流的开发方法论,分别从系统化管理和快速响应需求的角度为企业提供了解决方案。然而,单独使用其中一种方法往往无法完全满足企业在效率、质量和创新上的多重需...
华为IPD流程   35  
  华为IPD(Integrated Product Development,集成产品开发)流程是华为公司成功的关键因素之一。它不仅帮助华为在技术上实现了快速创新,还通过市场导向确保了产品的商业成功。IPD流程通过整合技术与市场双驱动,实现了从需求定义到产品交付的全生命周期管理。这种模式不仅提高了产品的开发效率,还降低了市...
IPD流程中PDCP是什么意思   32  
  在研发领域,集成产品开发(IPD)流程已经成为企业提升创新效率和市场竞争力的重要手段。然而,资源分配的不合理往往是制约IPD流程效率的关键因素之一。无论是人力资源、财务资源还是技术资源,如何高效分配直接关系到项目的成功与否。优化资源分配不仅能够缩短产品开发周期,还能降低研发成本,提升产品的市场竞争力。因此,掌握资源分配...
IPD流程中CDCP   34  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用