如何在Java代码中避免整数溢出?

31

可能是重复问题:
如何检查在Java中将两个数字相乘是否会导致溢出?

假设我有一个 Java 类的方法,使用了*+运算符。

int foo(int a, int b) {
  ... // 使用 + 和 * 进行一些计算
}

如何确保在foo中不会发生溢出?

我想我可以使用BigDecimal或者将所有+和*替换为“包装器”:

int sum(int a, int b) {
   int c = a + b;
   if (a > 0 && b > 0 && c < 0) 
     throw new MyOverfowException(a, b)
   return c;
}
int prod(int a, int b) { int c = a * b; if (a > 0 && b > 0 && c < 0) throw new MyOverfowException(a, b) return c; }

还有更好的方法可以确保在 Java 方法中不会发生int溢出吗?


3
为什么要检查是否小于零?一个整数可以远远低于0。 - 11684
@11684 我认为他对于 ab 是正整数有信心,因此如果结果为负数,则发生了溢出。 - Duncan Jones
2
请注意,即使ab都是正数,您的产品功能也无法正常工作,因为结果可能会溢出并仍然为正数,例如1000000000 * 15 = 2115098112 - verdesmarald
1
除非你真的需要使用除法,否则BigInteger比BigDecimal更好。 - MrLore
1
没有无符号类型真是糟糕,对吧?但如果发生溢出,你想让它怎么做呢?按定义,int是有界的,并且随着a和b足够大,它最终会溢出,无论你多么努力防止。如果发生溢出,它应该抛出异常吗?也许退回到使用BigInteger的重载版本? - Thomas
显示剩余6条评论
4个回答

23

检查溢出的一种方法是将操作数提升为更大的类型(原始操作数位长度的两倍),然后执行操作,再看结果值是否过大而超出了原始类型的范围。

int sum(int a, int b) {
    long r = (long)a + b;
    if (r >>> 32 != 0) {    // no sign extension
        throw new MyOverflowException(a, b);
    }
    return (int)r;
}
如果您的原始类型是long,那么您必须使用BigInteger作为更大的类型。

我知道这个答案很老,但是这个解决方案并不保证可行,因为更大的类型本身可能会溢出,并最终落入一个对于较小类型来说看起来正常的范围内。 - yitzih
@yitzih 你错了 - 两个(正)整数的相加结果不能超过最长操作数加1位的值。在问题的限制条件下,不可能出现“更大的类型本身溢出”的情况。 - Alnitak
@yitzihI 我忘记了乘法也是涉及到的,但即使如此,两个31位正整数的积也不会超过62位。 - Alnitak
这是一个公正的观点。我的错误,感谢您的澄清。 - yitzih

20

从工程角度来看,这是一个棘手的问题。

Secure Coding 网站建议:

  • 使用前置条件; 即对输入进行范围检查,以确保不会出现溢出情况,
  • 使用下一个更大的基本整数类型执行每个单独的算术运算,并明确检查溢出,或者
  • 使用BigInteger。

Dr Dobbs文章建议创建一个原始算术方法库,其中每个原始操作均采用显式溢出检查。 (您可以将此视为上述第2个要点的实现。)但作者进一步建议您使用字节码重写来将算术字节码替换为调用等效方法,这些方法包括溢出检查。

不幸的是,在Java中没有本地启用溢出检查的方法。(但同样适用于许多其他语言;例如C、C++ ...)


第二个是我的答案所做的。 - Alnitak
2
“从工程角度来看,这是一个棘手的问题。”-- 这并不难:只需在每个操作后生成机器代码以检查溢出寄存器标志。这就是C#中“checked”块所做的。问题在于Java没有提供这样的选项,而不是因为它超出了人类的智慧范围。 - Rich
1
@Rich - 如果你没有修改Java编译器的权限,那么这将会很困难。大多数人都没有这个权限! - Stephen C
1
笑,说得好 :-) 很让人沮丧,这不被包含在Java编译器中。关于这个问题或它的副本,没有一个漂亮简单并且正确的答案 :-( - Rich
http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/math/IntMath.html#checkedAdd(int, int) - Rich

7

求和:检查b是否大于int类型最大值减去a的值,如果a和/或b可以为负数,则需小心不要在差检查中已经溢出,并对最小值执行类似检查。

求积:这更困难。我将把整数分成两个半长度的整数(即,如果int是32位的,则使用位屏蔽和移位将其分成两个16位数字)。然后进行乘法,再查看结果是否适合32位。

前提是您不想简单地将long用作临时结果。


3
假设a和b都是正数或负数,如果a + b的符号与a和b的符号不相等,则会发生溢出。您可以使用此规则来判断是否发生溢出并抛出异常。当您捕获此异常时,可以根据之前答案中提到的方法处理它。 另一种方法是使用不会溢出的最大范围类型进行操作。您可以在Integer之间使用长整型进行操作。

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