【问题标题】:Can doubles or BigDecimal overflow?双打或 BigDecimal 会溢出吗?
【发布时间】:2015-11-05 15:07:57
【问题描述】:

Java 8 给了我们Math.addExact() 整数,但不是小数。

doubleBigDecimal 是否有可能溢出?从Double.MAX_VALUEHow to get biggest BigDecimal value 来看,答案是肯定的。

因此,为什么我们不为这些类型也设置Math.addExact()?自己检查这一点的最可维护的方法是什么?

【问题讨论】:

  • 理论上,任何有限精度都可能溢出。
  • 支持 double 的 addExact 要困难得多,因为它首先是一个近似表示。你甚至不能精确地添加 0.1 和 0.2。 BigDecimal 的溢出非常大,您可能首先会耗尽内存。
  • 如果双打你走得太远以至于它不能再被表示你会得到一个Infinity 值。那将是溢出。
  • @EdwinDalorzo 如果Infinity 已经是输入之一怎么办?这是浮点运算的有效输入。
  • Double 可以溢出,但它不像序数类型那样环绕。 underflow 浮点可以做什么,整数类型绝对不能做什么:一个非常接近于零的结果,它必须四舍五入为零。 BigDecimal 可以和你的内存一样大(或多或少),所以虽然理论上它可以溢出,但实际上它不会。除非您正在计算 Ackermann 函数的值 :)

标签: java double bigdecimal integer-overflow


【解决方案1】:

double 溢出到Infinity-Infinity,它不会环绕。 BigDecimal 不会溢出,期间,它仅受计算机内存量的限制。见:How to get biggest BigDecimal value

+.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 方法的原因。当然,您始终可以将自己的实现添加到自己应用程序中的实用程序类中。

【讨论】:

  • 所以要对 double 做同样的事情,你需要将 if 条件替换为 if (r == Double.POSITIVE_INFINITY || r == Double.NEGATIVE_INFINITY)?
  • 关于BigDecimal,理论上你是对的。在实践中,有一个实现限制:stackoverflow.com/a/6792114/14731
  • @Gili 不完全是,因为如果任何一个操作数都是无穷大,您会希望允许该结果 - 如果您添加 POSITIVE_INFINITY 和 NEGATIVE_INFINITY (这会导致 NaN,而不是无穷大),您会怎么做)?这应该算作“溢出”吗?基本上,双打具有更复杂的行为,因此将其巧妙地划分为“合理”和“不合理”的结果更加棘手。
  • 这并没有考虑到 ULP 的怪异——如果你有一个大于 2^53 的双精度数,并且你将 1.0 添加到它,结果就是原始数字——因为该大小的两个双打之间的最小距离> 1.0。应该_that_ 算作算术异常吗?因为如果确实如此,那么很多类似的事情就会以你可能不会想到的方式发生(即使是很小/合理的值)。
  • @durron597 是的,我的意思不是溢出,而是整数有一种特定的怪异(即溢出),所以很容易想出一个清晰的方法,其语义是“做加法,如果你遇到那个奇怪的地方就抛出一个异常;如果我没有得到一个异常,我会有一个直观的结果”使用双打,几乎整个事情很奇怪,所以你不能真正拥有那种帮助方法。
【解决方案2】:

浮点数不需要addExact 函数的原因是因为它没有环绕,而是溢出到Double.Infinity

因此,您可以很容易地检查在操作结束时是否溢出。由于Double.POSITIVE_INFINITY + Double.NEGATIVE_INFINITY 是 NaN,因此您还必须检查 NaN 以防更复杂的表达式。

这不仅更快,而且更易于阅读。不用Math.addExact(Math.addExact(x, y), z) 将 3 个双精度加在一起,您可以这样写:

double result = x + y + z;
if (Double.isInfinite(result) || Double.isNan(result)) throw ArithmeticException("overflow");
另一方面,

BigDecimal 确实会溢出并在这种情况下引发相应的异常 - 但实际上这不太可能发生。

【讨论】:

  • ...这是因为Infinity 上的操作定义明确,所以Infinity + any number apart from -Infinity 仍然是Infinity。您可以执行任何一系列操作,如果它在中间点溢出,您将永远不会得到错误但有效的结果:它将始终是无穷大或NaN 中的一个。这与可能发生这种情况的整数类型相反。
  • @biziclop 为了准确起见,您还必须检查 NaN 以防更复杂的表达式。
【解决方案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;
}

【讨论】:

    猜你喜欢
    • 2023-03-10
    • 2015-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-01
    • 2010-12-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多