📜  在Java中仔细分配长值以避免溢出

📅  最后修改于: 2022-05-13 01:55:15.782000             🧑  作者: Mango

在Java中仔细分配长值以避免溢出

预测以下程序的输出

public class LongDivision {
    public static void main(String[] args) {
    final long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000;
    final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;
    System.out.println(MICROS_PER_DAY / MILLIS_PER_DAY);
  
    }
}

解决方案:

除数和被除数都是 long 类型,它很容易大到足以容纳任何一个产品而不会溢出。那么,该程序似乎必须打印 1000。不幸的是,它打印了5 。这里到底发生了什么?问题是常量 MICROS_PER_DAY 的计算确实溢出了。虽然计算的结果适合 long 并留有余地,但它不适合 int。计算完全在 int 算术中执行,只有在计算完成后才会将结果提升为 long。到那时,为时已晚:计算已经溢出。
从 int 到 long 的提升是一种扩大的原始转换,它保留了(不正确的)数值。然后将该值除以 MILLIS_PER_DAY,因为它确实适合 int,所以计算正确。这个除法的结果是 5。那么为什么用 int 算术进行计算呢?因为所有相乘的因子都是 int 值。
当您将两个 int 值相乘时,您会得到另一个 int 值。 Java没有目标类型,这是一种语言特性,其中要存储结果的变量的类型会影响计算的类型。通过使用长字面量代替 int 作为每个产品的第一个因素,很容易修复程序。这会强制表达式中的所有后续计算都使用长算法完成。虽然只需要在 MICROS_PER_DAY 的表达式中执行此操作,但在两种产品中都执行此操作是一种很好的形式。
同样,并不总是需要在产品中使用 long 作为第一个值,但这样做是一种很好的形式。用 long 值开始这两个计算可以清楚地表明它们不会溢出。该程序按预期打印 1000:

public class LongDivision {
    public static void main(String[] args) {
    final long MICROS_PER_DAY = 24L * 60 * 60 * 1000 * 1000;
    final long MILLIS_PER_DAY = 24L * 60 * 60 * 1000;
    System.out.println(MICROS_PER_DAY / MILLIS_PER_DAY);
  
    }
}

输出:

1000

教训很简单:处理大数时,请注意溢出——它是一个无声的杀手。