Java中的取模运算会产生负数

184

在Java中计算int i = -1 % 2,结果为-1。而在Python中,计算-1 % 2的结果为1。 为了在Java中达到同样的效果,你需要使用如下代码:

int i = Math.floorMod(-1, 2);

6
等一下,这实际上是一个重复的问题。它已经有了一个完美的答案:https://dev59.com/12855IYBdhLWcg3wUSgW#4412200 - Val
1
@Val,你提到了模n同余类:这个范围{0,1,2..n-1}对程序员来说很好,但{-n,n+1,n+2,-1}是等价的,有同样存在的权利。 - Timofey
2
毫无疑问,这个运算符的俗称“mod”(可能是源自C家族?)是导致混淆的一部分。实际上,Java文档将其称为“remainder”运算符(https://docs.oracle.com/javase/tutorial/java/nutsandbolts/op1.html)。 - LarsH
3
K&R C将%运算符定义为产生余数,但将其称为模数运算符。令人困惑的是,https://en.wikipedia.org/wiki/Modulo_operation声称*模数*运算在计算和数学中都产生**余数**,但也声称“整数取模n的数字范围是0到n-1”。 - LarsH
Java中没有模数运算符。%是一个余数运算符。 - user207421
5个回答

207

这里的问题在于,在Python中,%运算符返回模数(modulus),而在Java中它返回余数(remainder)。正数参数下这些函数会给出相同的结果,但对于负数输入,模数总是返回正结果,而余数可能会给出负结果。在这个问题中有更多相关信息。

您可以通过以下方式找到正值:

int i = (((-1 % 2) + 2) % 2)

或者这样:

int i = -1 % 2;
if (i<0) i += 2;

(显然,-1或2可以是任何您想要的分子或分母)


2
@amit_gr - 不,我相信它通常是有效的。 - andrewmu
5
иҜ·жҸҗдҫӣдёҖдёӘдҫӢеӯҗжқҘж”ҜжҢҒиҝҷдёӘиҜҙжі•гҖӮжҲ‘и®ӨдёәеҺҹеё–зҡ„и§ЈеҶіж–№жЎҲе·Із»ҸеҫҲйҖҡз”ЁдәҶпјҢиҖғиҷ‘еҲ°(((-3 % 4) + 4) % 4) = 1(жүҖжңҹжңӣзҡ„з»“жһң)пјҢд»ҘеҸҠ(((3 % 4) + 4) % 4) = 3(еҗҢж ·жҳҜжүҖжңҹжңӣзҡ„з»“жһң)гҖӮе®ғйҖӮз”ЁдәҺжӯЈж•°е’Ңиҙҹж•°йҷӨж•°гҖӮ - The111
2
想象一下,如果你的整数范围是[-8,7],那么(((5 % 6) + 6) % 6) = ((5 + 6) % 6) = (-5 % 6) = -5,但是5 % 6应该是正的。用32位整数适当地替换为像536887296和1610612736这样的大数,第二种方法显然更好一些。 - Greg Rogers
1
不是模数,而是余数。请修正你的答案。 - isekaijin
4
假设 n=-1000m=3,正确的答案应该是 2,但是根据你的公式计算得到的答案仍然为负数。 - Felipe Nardi Batista
显示剩余12条评论

151

2

如果你需要 n % m,则:

int i = (n < 0) ? (m - (abs(n) % m) ) %m : (n % m);

数学解释:

n = -1 * abs(n)
-> n % m = (-1 * abs(n) ) % m
-> (-1 * (abs(n) % m) ) % m
-> m - (abs(n) % m))

这个表达式对我没用。对于负值,我得到的值在1:m之间,而不是期望的0:m-1,就像n为正数的情况一样。andrewmu的解决方案按预期运行。 - Cachapa

2
if b > 0:
    int mod = (mod = a % b) < 0 ? a + b : a;

不要两次使用 % 运算符。

这个版本的速度与使用两个%运算符的版本相比如何? - Christian
这是一个很好的问题。我经常进行过早的优化。我认为这可以节省几个 CPU 周期。 - Dico
1
@Dice:如果您能充分论证此解决方案优于当前已接受的解决方案,那对于浏览此问题的人们将是非常有价值的。 - Christian
2
无论是if还是%快,这取决于你的CPU和你输入的数据,因为分支预测的原因-如果条件具有可预测的模式,则if更快。 - Vitruvie
2
这几乎肯定会更慢,因为它有一个分支,除非你知道输入大多数是正数。如果是随机的,则分支预测惩罚将比大多数CPU上的额外余数计算花费更多的时钟周期。或者,如果您想根据整数是否为负来有条件地添加值,请尝试(maybeNegative >> 31) ^ thingToMaybeAdd + thingToAddTo - Scott Carey

1
如果模数是2的幂,则可以使用位掩码:
int i = -1 & ~-2; // -1 MOD 2 is 1

相比之下,Pascal语言提供了两个运算符;REM取分子的符号(x REM yx - (x DIV y) * y,其中x DIV yTRUNC(x / y)),而MOD需要一个正的分母并返回一个正的结果。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接