为什么用科学记数法写一个数字会使这段代码有所不同?

Why does writing a number in scientific notation make a difference in this code?

我正在尝试编写代码来确定自 1970 年开始以来的毫秒数何时会超过 long 的容量。以下代码似乎可以完成这项工作:

public class Y2K {
    public static void main(String[] args) {
        int year = 1970;
        long cumSeconds = 0;

        while (cumSeconds < Long.MAX_VALUE) {
            // 31557600000 is the number of milliseconds in a year
            cumSeconds += 3.15576E+10;
            year++;
        }
        System.out.println(year);
    }
}

这段代码在几秒钟内执行并打印 292272992。如果我不使用科学记数法而是将 cumSeconds 写为 31558000000L,程序似乎“永远”到 运行(我只是在之后点击暂停10 分钟左右)。另请注意,以科学记数法书写 cumSeconds 不需要指定数字是 long 且末尾带有 L 或 l。

之所以有所不同,是因为科学计数法数字 3.1558E+10double 文字,而文字 31558000000L 当然是 long 文字。

这使 the += operator 变得完全不同。

A compound assignment expression of the form E1 op= E2 is equivalent to E1 = (T) ((E1) op (E2)), where T is the type of E1, except that E1 is evaluated only once.

基本上,long += long 产生一个 long,但 long += double 也产生一个 long。

添加double时,cumSeconds的初始值被加宽为double,然后进行添加。结果经历 narrowing primitive conversion 回到 long

A narrowing conversion of a floating-point number to an integral type T takes two steps:

  1. In the first step, the floating-point number is converted either to a long, if T is long

(剪断)

  • Otherwise, one of the following two cases must be true:

    • The value must be too small (a negative value of large magnitude or negative infinity), and the result of the first step is the smallest representable value of type int or long.

    • The value must be too large (a positive value of large magnitude or positive infinity), and the result of the first step is the largest representable value of type int or long.

(大胆强调我的)

最终结果太大,无法用 long 表示,因此结果缩小为 Long.MAX_VALUEwhile 循环结束。

然而,当你使用一个long字面量时,你是在不断地把一个偶数加到一个偶数上,最终会溢出。这不会将值设置为 Long.MAX_VALUE,这是奇数,因此循环是无限的。

但是,在 Java 1.8+ 中,您可以使用 Math.addExact.

显式测试溢出,而不是依赖最终产生 Long.MAX_VALUE 的加法

Returns the sum of its arguments, throwing an exception if the result overflows a long.

Throws:

ArithmeticException - if the result overflows a long

关键观察是 cumSeconds < Long.MAX_VALUE 其中 cumSecondslong 只有当 cumSeconds 正好是 Long.MAX_VALUE 时才为假。

如果您使用长数字进行计算,则需要相当长的时间才能准确地达到此值(如果曾经达到过),因为当您离开数字范围时,长算法会回绕。

当双精度值足够大时,用双精度数进行运算将得到最大值。

@rgettman 已经详细介绍了当您使用 double 而不是 long 时发生的圆周操。但还有更多。

当您反复将一个大数字添加到 long 时,您最终会得到一个负数结果。例如,Long.MAX_VALUE + 1L = Long.MIN_VALUE。发生这种情况时,您将无限期地重复该过程。

因此,如果您将代码更改为:

    while (cumSeconds >= 0L) {
        // 31557600000 is the number of milliseconds in a year
        cumSeconds += 31557600000L;

你会发现事情变得消极的地方,因为 cumSeconds 翻车了。