¿Por qué escribir un número en notación científica hace una diferencia en este código?


Estoy tratando de escribir un código para determinar cuándo el número de milisegundos desde el comienzo de 1970 excederá la capacidad de un largo. El siguiente código parece hacer el trabajo:

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);
    }
}

Este código se ejecuta en segundos e imprime 292272992. Si en lugar de usar notación científica escribo cumSeconds como 31558000000L, el programa parece tardar "para siempre" en ejecutarse (solo presiono pause después de 10 minutos aproximadamente). También tenga en cuenta que escribir cumSeconds en notación científica no requiere especificando que el número es un long con L o l al final.

Author: sedeh, 2015-09-18

3 answers

La razón por la que hace una diferencia es porque el número de notación científica 3.1558E+10 es un double literal, mientras que el literal 31558000000L es, por supuesto, un long literal.

Esto hace toda la diferencia en el operador += .

Una expresión de asignación compuesta de la forma E1 op= E2 es equivalente a E1 = (T) ((E1) op (E2)), donde T es el tipo de E1, excepto que E1 se evalúa solo una vez.

Básicamente, long + = long produce un long, pero long += doble también produce un largo.

Cuando se agrega un double, el valor inicial de cumSeconds se amplía a un double y luego se produce la adición. El resultado sufre una reducción de la conversión primitiva de vuelta a long.

Una conversión de estrechamiento de un número de coma flotante a un tipo integral T toma dos pasos:

  1. En el primer paso, el número de coma flotante se convierte en un largo, si T es largo

(snip)

  • De lo contrario, uno de los dos casos siguientes debe ser verdadero:{[18]]}

    • El valor debe ser demasiado pequeño (un valor negativo de gran magnitud o infinito negativo), y el resultado del primer paso es el valor representable más pequeño de tipo int o long.

    • El valor debe ser demasiado grande (un valor positivo de gran magnitud o infinito positivo), y el resultado del primer paso es el mayor valor representable de tipo int o long .

(negrita énfasis mío)

El resultado finalmente es demasiado grande para ser representado en un long, por lo que el resultado se reduce a Long.MAX_VALUE, y el bucle while termina.

Sin embargo, cuando usa un literal long, está agregando continuamente un valor par a un valor par, que eventualmente se desbordará. Esto no establece el valor a Long.MAX_VALUE, que es impar, por lo que el bucle es infinito.

Pero en lugar de confiar en una adición finalmente rindiendo Long.MAX_VALUE, con Java 1.8 + puede probar explícitamente el desbordamiento con Math.addExact.

Devuelve la suma de sus argumentos, lanzando una excepción si el resultado se desborda a long.

Lanza:

ArithmeticException - si el resultado desborda un

 41
Author: rgettman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-25 18:26:57

La observación clave es que cumSeconds < Long.MAX_VALUE donde cumSeconds es un long solo puede ser falso si cumSeconds es exactamente Long.MAX_VALUE.

Si haces el cálculo con números largos, toma bastante tiempo alcanzar este valor exactamente (si es que alguna vez se alcanza) porque la aritmética larga se envuelve cuando dejas el rango numérico.

Hacer la aritmética con números dobles dará como resultado el valor máximo cuando el valor doble sea lo suficientemente grande.

 6
Author: Henry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-18 18:16:22

@rgettman ya ha entrado en detalles sobre la gimnasia de redondeo que se lleva a cabo cuando se usa un double en lugar de un long. Pero hay más.

Cuando agrega repetidamente un número grande a un long, eventualmente terminará con un resultado negativo. Por ejemplo, Long.MAX_VALUE + 1L = Long.MIN_VALUE. Cuando eso suceda, repetirás el proceso indefinidamente.

Así que si cambiaste tu código a:

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

Cogerás donde las cosas van negativas porque cumSeconds se volcó.

 5
Author: Erick G. Hagstrom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-18 21:16:09