双精度浮点数或BigDecimal会发生溢出吗?

20

Java 8为整数提供了Math.addExact(),但没有针对小数。

doubleBigDecimal是否可能溢出?根据Double.MAX_VALUE如何获取最大的BigDecimal值来判断,答案是肯定的。

既然如此,为什么我们不为这些类型提供Math.addExact()呢?自己检查这一点最可维护的方法是什么?


2
任何有限精度的东西理论上都可能溢出。 - David W
5
支持double的addExact非常困难,因为它本身就是一种近似表示。你甚至不能准确地添加0.1和0.2。BigDecimal的溢出范围非常大,你可能会先耗尽内存。 - Peter Lawrey
2
如果使用双精度浮点数超出了其可表示范围,你将得到一个“无穷大”的值。这就是溢出。 - Edwin Dalorzo
@EdwinDalorzo 如果“无穷大”已经是输入之一,该怎么办?这是浮点运算的有效输入。 - rgettman
1
Double类型可以溢出,但不像普通类型那样循环。浮点数能够做到的是,整数绝对不能做到的是下溢:即结果非常接近于零,必须四舍五入为零。BigDecimal的大小可以达到内存所能承载的极限(或者说差不多),因此,虽然它在理论上可能会溢出,在实际应用中不会发生。除非你要计算 Ackermann 函数的值 :) - biziclop
3个回答

17

double会溢出成正无穷或负无穷,而不是环绕回来。 BigDecimal 不会溢出,它的限制仅取决于计算机中的内存量。请参阅:如何获取最大的BigDecimal值

+.addExact 的唯一区别在于它尝试检测是否发生了溢出,并抛出异常而不是环绕。 这是源代码:

public static int addExact(int x, int y) {
    int r = x + y;
    // HD 2-12 Overflow iff both arguments have the opposite sign of the result
    if (((x ^ r) & (y ^ r)) < 0) {
        throw new ArithmeticException("integer overflow");
    }
    return r;
}

如果你想检查溢出是否发生,从某种意义上说,使用 double 更简单,因为你可以简单地检查 Double.POSITIVE_INFINITYDouble.NEGATIVE_INFINITY;对于 intlong,这是一个稍微复杂些的问题,因为它并不总是一个固定的值,但在另一方面,这些可能是输入(例如 Infinity + 10 = Infinity,在这种情况下您可能不想抛出异常)。

由于所有这些原因(我们甚至还没有提到 NaN),这可能就是为什么在 JDK 中不存在这样一个addExact方法。当然,你总是可以在你自己的应用程序中将你自己的实现添加到一个实用类中。


1
那么如果要对double执行相同的操作,您需要将if条件替换为if (r == Double.POSITIVE_INFINITY || r == Double.NEGATIVE_INFINITY) - Gili
关于 BigDecimal,从理论上讲你是正确的。但实际上,存在一些实现限制:https://dev59.com/emw15IYBdhLWcg3wJ4is#6792114。 - Gili
@Gili 不完全正确,因为如果任何操作数是无穷大,您都希望允许该结果 - 如果您将POSITIVE_INFINITY和NEGATIVE_INFINITY相加会发生什么(结果不是任何一个无穷大的NaN)?这是否应该算作“溢出”?基本上,双精度具有更复杂的行为,因此更难以将其整齐地分为“合理”和“不合理”的结果。 - yshavit
1
这还没有考虑到 ULP 的怪异性——如果您有一个大于2^53的双精度数,然后将1.0添加到它上面,结果是原始数字,因为在那个大小的两个双精度数之间的最小距离> 1.0。这应该算作算术异常吗?因为如果这样做,那么许多类似的事情也会以您可能不期望的方式发生(即使在小/合理值上也是如此)。 - yshavit
1
@durron597 是的,我的观点并不是说这是溢出,而是对于整数来说有一种特定的怪异现象(即溢出),因此很容易想出一个清晰的方法,其语义是“进行加法运算,并在遇到这种奇怪情况时抛出异常;如果我没有收到异常,我将得到一个直观的结果”。对于双精度浮点数,几乎整个过程都是奇怪的,因此你不能真正拥有那种类型的辅助方法。 - yshavit
显示剩余4条评论

5
您不需要为浮点数编写addExact函数的原因是,它不会绕回,而是溢出到Double.Infinity
因此,在操作结束时,您可以非常轻松地检查它是否溢出。由于Double.POSITIVE_INFINITY + Double.NEGATIVE_INFINITY为NaN,因此在更复杂的表达式中还必须检查NaN。
这不仅更快,而且更易读。例如,将3个双精度数相加,您可以这样写:Math.addExact(Math.addExact(x, y), z)
double result = x + y + z;
if (Double.isInfinite(result) || Double.isNan(result)) throw ArithmeticException("overflow");

BigDecimal与之相反,在这种情况下确实会溢出并抛出相应的异常——但在实践中,这种情况非常不可能发生。


2
这是由于操作Infinity已经被很好地定义,所以Infinity + 任何除了 -Infinity 的数仍将是Infinity。您可以执行任何一系列操作,如果在中间点上溢出,您永远不会得到一个虚假但有效的结果:它总是会成为其中一个无限大或NaN。这与整数类型相反,因为在整数类型中,可能会发生这种情况。 - biziclop
@biziclop 为了更准确,对于更复杂的表达式,您还需要检查NaN - Voo

3

对于double,请查看其他答案。

BigDecimal已经内置了addExact()保护。 BigDecimal的许多算术操作方法(例如multiply)都包含对结果比例的检查:

private int checkScale(long val) {
    int asInt = (int)val;
    if (asInt != val) {
        asInt = val>Integer.MAX_VALUE ? Integer.MAX_VALUE : Integer.MIN_VALUE;
        BigInteger b;
        if (intCompact != 0 &&
            ((b = intVal) == null || b.signum() != 0))
            throw new ArithmeticException(asInt>0 ? "Underflow":"Overflow");
    }
    return asInt;
}

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