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



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

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 不需要指定数字是末尾带有 L 或 l 的long

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

这使得+=运算符大不相同。

形式为 E1 op= E2 的复合赋值表达式等效于 E1 = (T( ((E1(

op (E2((,其中 T 是 E1 的类型,只是 E1 只计算一次。

基本上,多头 += 多头产生多头

,但多头 += 双倍也产生多头。

当添加double时,cumSeconds的初始值被扩大到double然后进行加法。 结果经历了缩小的原始转换回long

浮点数缩小到整数类型 T 的转换需要两个步骤:

  1. 在第一步中,如果 T 长,则将浮点数转换为长整型

(截图(

  • 否则,必须满足以下两种情况之一:

    • 该值必须太小(大量级或负无穷大的负值(,第一步的结果是 int 或 long 类型的最小可表示值。

    • 该值必须太大(大量级或正无穷大的正值(,并且第一步的结果是 int 或 long 类型的最大可表示值

(粗体强调我的(

结果最终太大而无法用long表示,所以结果缩小到Long.MAX_VALUEwhile循环结束。

但是,当您使用long文本时,您不断将偶数值添加到偶数值,该值最终将溢出。 这不会将值设置为 Long.MAX_VALUE ,这是奇数,因此循环是无限的。

但是,与其依赖最终产生Long.MAX_VALUE的加法,不如使用Java 1.8+显式测试Math.addExact溢出。

返回其参数的总和,如果结果溢出 long,则引发异常。

抛出:

ArithmeticException - 如果结果溢出很长时间

关键的观察结果是,cumSecondslongcumSeconds < Long.MAX_VALUE只有在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翻身了。

相关内容

  • 没有找到相关文章

最新更新