使Java的模数表现为负数的最佳方式?


103

在java中,当你做

a % b

如果a为负数,它将返回负数结果,而不是像应该包裹到b那样。解决此问题的最佳方法是什么?我认为唯一的方法是

a < 0 ? b + a : a % b

12
处理负数时没有“正确的”模量行为-许多语言都这样做,许多语言都不同,有些语言则完全不同。至少前两个有其优点和缺点。

4
这对我来说很奇怪。我以为,如果b为负,它应该只返回负。
fent


2
它是。但该问题的标题应重命名。如果我正在搜索这个问题,我不会单击该问题,因为我已经知道java模数是如何工作的。
fent

4
我只是将其重命名为“为什么-13%64 = 51?”,所以在一百万年之内永远不会有人搜索。因此,此问题标题要好得多,并且在诸如模数,否定,计算,数字之类的关键字上可搜索得多。
埃里克·罗伯逊

Answers:


144

它的行为应为%b = a-a / b * b; 即是剩余的。

你可以做(​​a%b + b)%b


该表达式的工作结果(a % b)是必须小于b,无论a是正数还是负数。加法b照顾的负值a,因为(a % b)-b和之间的负值0(a % b + b)必然小于b和正值。万一a开始是正数,则存在最后一个模,因为如果a正数(a % b + b)将大于b。因此,(a % b + b) % b将其变为小于b(而不影响负值a)。


3
效果更好,谢谢。并且它也适用于比b大得多的负数。
fent

6
(a % b)之所以起作用,是因为结果一定要小于b(无论a是正数还是负数),加法b处理的负值a,因为(a % b)小于b和小于0(a % b + b)必然小于b和是正数。万一a开始是正数,则存在最后一个模,因为如果a正数(a % b + b)将大于b。因此,(a % b + b) % b将其变为小于b(而不影响负值a)。
ethanfar

1
@eitanfar我已经在答案中包含了您的出色解释(稍作修正a < 0,也许您可​​以看看)
Maarten Bodewes 2014年

5
我刚刚看到这评论了关于同一主题的另一个问题。值得一提的是(a % b + b) % b,对于a和很大的值,它会分解b。例如,使用a = Integer.MAX_VALUE - 1b = Integer.MAX_VALUE将给出-3结果,这是一个负数,这是您要避免的。
Thorbear

2
while如果确实需要@Mikepote,则使用a的速度会较慢,除非只需要使用a ,否则if实际上会更快。
彼得·劳瑞

92

从Java 8开始,您可以使用Math.floorMod(int x,int y)Math.floorMod(long x,long y)。这两种方法都返回与Peter答案相同的结果。

Math.floorMod( 2,  3) =  2
Math.floorMod(-2,  3) =  1
Math.floorMod( 2, -3) = -1
Math.floorMod(-2, -3) = -2

1
Java 8+的最佳答案
Charney Kaye

酷,不知道那一个。Java 8最终修复了一些PITA。
Franz D.

4
好办法。但不幸的是,这不适用于floatdouble参数。Mod二进制运算符%)也可用于floatdouble操作数。
Mir-Ismaili

11

对于尚未使用(或无法使用)Java 8的用户,Guava借助IntMath.mod()进行了抢救,该工具自Guava 11.0起可用。

IntMath.mod( 2, 3) = 2
IntMath.mod(-2, 3) = 1

一个警告:与Java 8的Math.floorMod()不同,除数(第二个参数)不能为负。


7

在数论中,结果总是正的。我猜想在计算机语言中并非总是如此,因为并非所有程序员都是数学家。我的两分钱,我会认为它是该语言的设计缺陷,但您现在不能更改。

= MOD(-4,180)= 176 = MOD(176,180)= 176

因为180 *(-1)+ 176 = -4与180 * 0 + 176 = 176相同

使用此处的时钟示例http://mathworld.wolfram.com/Congruence.html ,即使两个答案都满足基本方程式,您也不会说duration_of_time mod cycle_length是-45分钟,而是15分钟。


1
在数论中,它并不总是积极的……它们属于同余类。您可以随意从该类别中选择任何候选者用于您的符号目的,但是其思想是它可以映射到该类别的所有候选者,并且如果使用特定的其他候选者,则可以使某些问题变得更加简单(例如,选择-1而不是n-1)然后就可以了。
BeUndead

2

Java 8具有Math.floorMod,但是它非常慢(其实现具有多个除法,乘法和有条件的)。但是,JVM可能具有针对其进行内部优化的存根,这将显着提高其速度。

没有此操作的最快方法与floorMod此处的其他答案类似,但没有条件分支,只有一个慢%操作。

假设n为正,并且x可以是任何值:

int remainder = (x % n); // may be negative if x is negative
//if remainder is negative, adds n, otherwise adds 0
return ((remainder >> 31) & n) + remainder;

结果何时n = 3

x | result
----------
-4| 2
-3| 0
-2| 1
-1| 2
 0| 0
 1| 1
 2| 2
 3| 0
 4| 1

如果您只需要在0和之间分配一个统一的分配器,n-1而不是确切的mod运算符,并且您x的分配器不会在附近聚集0,那么随着指令级并行度的提高和%运算速度的降低,以下方法将变得更快。零件,因为它们不取决于其结果。

return ((x >> 31) & (n - 1)) + (x % n)

上面的结果为n = 3

x | result
----------
-5| 0
-4| 1
-3| 2
-2| 0
-1| 1
 0| 0
 1| 1
 2| 2
 3| 0
 4| 1
 5| 2

如果输入在int的整个范围内都是随机的,则两个解的分布将相同。如果输入簇接近零,则n - 1后一种解决方案的结果将太少。


1

这是一个替代方案:

a < 0 ? b-1 - (-a-1) % b : a % b

这可能会或可能不会比其他公式[(a%b + b)%b]快。与其他公式不同,它包含一个分支,但是使用较少的取模运算。如果计算机可以正确预测<0,则可能是一场胜利。

(编辑:修正了公式。)


1
但是模运算需要除法,该除法甚至可能更慢(尤其是如果处理器几乎一直在正确猜测分支的情况下)。因此,这可能更好。
戴夫

@KarstenR。你是对的!我固定了公式,现在可以正常工作(但需要再减去两个)。
Stefan Reich

@dave是真的
Stefan Reich
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.