Java中的求模与取余

内容纲要

问题引入

在一次解决redis热key问题时,打算通过计算"string".hashCode()%10对key进行分散,打算放到10个不同的key中,代码如下所示,

public class Main
{
    public static void main(String[] args) {
        System.out.println("xxxxxxxx_id_0".hashCode()%10);
        System.out.println("xxxxxxxx_id_1".hashCode()%10);
        System.out.println("xxxxxxxx_id_2".hashCode()%10);
        System.out.println("xxxxxxxx_id_3".hashCode()%10);
        System.out.println("xxxxxxxx_id_4".hashCode()%10);
        System.out.println("xxxxxxxx_id_5".hashCode()%10);
        System.out.println("xxxxxxxx_id_6".hashCode()%10);
        System.out.println("xxxxxxxx_id_7".hashCode()%10);
        System.out.println("xxxxxxxx_id_8".hashCode()%10);
        System.out.println("xxxxxxxx_id_9".hashCode()%10);
        System.out.println("xxxxxxxx_id_10".hashCode()%10);
        System.out.println("xxxxxxxx_id_11".hashCode()%10);
        System.out.println("xxxxxxxx_id_12".hashCode()%10);
        System.out.println("xxxxxxxx_id_13".hashCode()%10);
    }
}

运行结果如下:

标准输出:
-1
0
-9
-8
-7
-6
-5
-4
-3
-2
8
9
0
1

通过观察结果可知,在java中取%时,在被除数为正数时,结果可能为负数,本来要分为10个key,结果分散到19个key中,导致意外的bug

求模与取余的概念

  • 取余运算(“Remainder Operation ”),一般使用rem表示,求余(rem)时的商是向0取整(java的%即为向0取整)
  • 取模运算(“Modulo Operation”),一般使用mod表示,求模(mod)时的商是向下取整(向负无穷方向取整)

求模与取余的结果表格对照

假定被除数为a,除数为b

求余表达式 求余结果 求模表达式 求模结果 结论
7 rem 4 1 3 7 mod 4 1 3 a、b符号相同,求余和求模结果相同
(-7) rem 4 -1 -3 (-7) mod 4 -2 1 a、b符号不同,求余和求模结果不同
7 rem (-4) 1 3 7 mod (-4) -2 -1 a、b符号不同,求余和求模结果不同
(-7) rem (-4) -1 -3 (-7) mod (-4) -1 -3 a、b符号相同,求余和求模结果相同

求余操作的结果的符号取决于被除数的符号
在编译器中,两个异号的数取余之后的结果取决于分子的符号。负数%负数,编译器会将分母的负数自动转换为正整数,然后再将分子负数的负号提取出来,将两个正整数取余,最后的结果加上负号就好了。负数%正数,编译器先将分子负数的负号提取出来,将两个正整数取余,最后结果加上负号即可。正数%负数,编译器自动将分母负数转换为正整数,然后两个正整数取余得到就是最终结果

求模操作的结果的符号取决于除数的符号

java代码验证

java在线运行平台

public class Main
{
    public static void main(String[] args)
    {
        System.out.println("------Remainder Operation------");
        System.out.println(7 % 4);
        System.out.println((-7) % 4);
        System.out.println(7 % (-4));
        System.out.println((-7) % (-4));
        System.out.println("------Modulo Operation------");
        System.out.println(Math.floorMod(7, 4));
        System.out.println(Math.floorMod(-7, 4));
        System.out.println(Math.floorMod(7, -4));
        System.out.println(Math.floorMod(-7, -4));
    }
}

输出值:

标准输出:
------Remainder Operation------
3
-3
3
-3
------Modulo Operation------
3
1
-1
-3

总结

对于整型数a,b来说,取模运算或者求余运算的方法都是:

  1. 求 整数商: c = a/b;
  2. 计算模或者余数: r = a – c*b.求模运算和求余运算在第一步不同:取余运算在取c的值时,向0 方向舍入(fix()函数);而取模运算在计算c的值时,向负无穷方向舍入(floor()函数)

例如计算:-7 Mod 4那么:a = -7;b = 4;

  • 第一步:求整数商c,如进行求模运算c = -2(向负无穷方向舍入),求余c = -1(向0方向舍入);
  • 第二步:计算模和余数的公式相同,但因c的值不同,求模时r = 1,求余时r = -3。
  • 归纳:当a和b符号一致时,求模运算和求余运算所得的c的值一致,因此结果一致。当符号不一致时,结果不一样。求模运算结果的符号和b一致,求余运算结果的符号和a一致。另外各个环境下%运算符的含义不同,比如c/oc/c++,java 为取余,而python则为取模。

问题解决方法

根据上述可知:求模操作的结果的符号取决于除数的符号,因此我们应该将最开始代码中的%修改为Math.floorMod

public class Main
{
    public static void main(String[] args) {
        System.out.println(Math.floorMod("xxxxxxxx_id_0".hashCode(),10));
        System.out.println(Math.floorMod("xxxxxxxx_id_1".hashCode(),10));
        System.out.println(Math.floorMod("xxxxxxxx_id_2".hashCode(),10));
        System.out.println(Math.floorMod("xxxxxxxx_id_3".hashCode(),10));
        System.out.println(Math.floorMod("xxxxxxxx_id_4".hashCode(),10));
        System.out.println(Math.floorMod("xxxxxxxx_id_5".hashCode(),10));
        System.out.println(Math.floorMod("xxxxxxxx_id_6".hashCode(),10));
        System.out.println(Math.floorMod("xxxxxxxx_id_7".hashCode(),10));
        System.out.println(Math.floorMod("xxxxxxxx_id_8".hashCode(),10));
        System.out.println(Math.floorMod("xxxxxxxx_id_9".hashCode(),10));
        System.out.println(Math.floorMod("xxxxxxxx_id_10".hashCode(),10));
        System.out.println(Math.floorMod("xxxxxxxx_id_11".hashCode(),10));
        System.out.println(Math.floorMod("xxxxxxxx_id_12".hashCode(),10));
        System.out.println(Math.floorMod("xxxxxxxx_id_13".hashCode(),10));
    }
}

结果如下:

标准输出:
9
0
1
2
3
4
5
6
7
8
8
9
0
1

参考文档

取余和取模的区别:https://www.runoob.com/w3cnote/remainder-and-the-modulo.html
百度百科:https://baike.baidu.com/item/%E5%8F%96%E6%A8%A1%E8%BF%90%E7%AE%97/10739384?fr=aladdin
Java取模和取余,你真的弄懂了吗:https://www.cnblogs.com/doondo/p/14678204.html
编程语言中的 % 是取模 还是 取余?:https://cloud.tencent.com/developer/news/267970
负数取余问题:https://blog.csdn.net/qq_43152052/article/details/101023628

Java中的求模与取余

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

滚动到顶部