在 Python 中模数(%)运算符如何处理负数?
- 2024-12-12 08:40:00
- admin 原创
- 99
问题描述:
在 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
數量 | 答 |
---|---|
3 | 0 |
2 | 2 |
1 | 1 |
0 | 0 |
-1 | 2 |
-2 | 1 |
-3 | 0 |
解决方案 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
,用文字来解释一下?
假设a
和b
是float
和/或int
值,有限的(不是math.inf
,math.nan
等等)并且b
不为零......
结果c
是具有 符号的唯一数字,使得能被和整除。当和均为时,它将为,而当 或 为 int 时,它将为(即使它恰好等于整数) 。b
a - c
`babs(c) < abs(b)
inta
b
intfloat` `a
b`
例如:
>>> -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-3
3-1
2`2
>>> -1 % 3
2
b
那么等于零怎么样?
raise ZeroDivisionError
无论是b
整数零、浮点正零还是浮点负零,结果都是 。特别是,它不会导致 NaN 值。
那么特殊浮点值呢?
正如人们所料,只要不为零(覆盖其他所有内容), 的nan
有符号无穷大值就会a
产生结果。的值也会导致。 NaN 不能带符号,因此在这些情况下 的符号无关紧要。nan
`bnan
bnan
b`
也可以预料,无论符号如何,inf % inf
都会给出nan
。如果我们将无限数量的 s 分配a
给无限数量的b
s ,则无法说出“哪个无穷大更大”或大多少。
唯一稍微令人困惑的情况是当b
是有符号无穷大值时:
>>> 0 % inf
0.0
>>> 0 % -inf
-0.0
>>> 1 % inf
1.0
>>> 1 % -inf
-inf
与往常一样,结果采用 的符号b
。0
可以被任何东西(NaN 除外)整除,包括无穷大。但没有其他任何东西可以整除无穷大。如果a
具有与 相同的符号b
,则结果就是a
(作为浮点值);如果符号不同,则结果为b
。为什么?好吧,考虑-1 % inf
。没有一个有限的值可以从 中减去-1
,以得到0
(我们可以除以无穷大的唯一值)。所以我们必须继续下去,直到无穷大。同样的逻辑适用于1 % -inf
,但所有符号都反转。
那么其他类型呢?
这取决于类型。例如,Decimal 类型重载了运算符,因此结果采用分子的符号,即使它在功能上表示与 a 相同类型的值float
。当然,字符串将其用于完全不同的东西。
为什么不总是给出积极的结果,或者采取的符号a
?
这种行为是由整数除法引起的。虽然%
它恰好适用于浮点数,但它是专门为处理整数输入而设计的,float
s 的结果与此一致。
在选择给出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 认为这并不重要。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 项目管理必备:盘点2024年13款好用的项目管理软件
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)