为什么用科学记数法编写数字会对此代码产生影响?

时间:2015-09-18 18:01:11

标签: java long-integer scientific-notation

我正在尝试编写一个代码来确定自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 个答案:

答案 0 :(得分:41)

它产生影响的原因是因为科学记数3.1558E+10double字面值,而字面31558000000L当然是long字面值。

这在the += operator中有所不同。

  

E1 op = E2形式的复合赋值表达式等效于E1 =(T)((E1)op(E2)),其中T是E1的类型,但E1仅被评估一次。

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

添加double时,cumSeconds的初始值会扩展为double,然后会发生添加。结果会narrowing primitive conversion返回long

  

将浮点数转换为整数类型T需要两个步骤:

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

(剪断)

  
      
  • 否则,以下两种情况之一必须为真:

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

    •   
    • 值必须太大(大幅度或正无穷大的正值),第一步的结果是最大可表示的类型int或long 。< / p>

    •   
  •   

(大胆强调我的)

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

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

但是,使用Java 1.8+而不依赖于最终产生Long.MAX_VALUE的添加,您可以使用Math.addExact显式测试溢出。

  

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

     

<强>抛出:

     

ArithmeticException - 如果结果溢出了很长的

答案 1 :(得分:6)

关键的观察结果是cumSeconds < Long.MAX_VALUE其中cumSecondslong只有cumSeconds正好Long.MAX_VALUE才能为假。

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

当double值足够大时,使用双数进行算术将产生最大值。

答案 2 :(得分:5)

@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已经过去了。