2015-09-18 10 views
24

Sto provando a scrivere un codice per determinare quando il numero di millisecondi dall'inizio del 1970 supererà la capacità di un lungo. Il seguente codice sembra fare il lavoro:Perché scrivere un numero in notazione scientifica fa la differenza in questo codice?

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

Questo codice viene eseguito in pochi secondi e stampe 292272992. Se invece di usare la notazione scientifica scrivo cumSeconds come 31558000000L, il programma sembra prendere “per sempre” a correre (ho appena colpisci la pausa dopo 10 minuti circa). Si noti inoltre che scrivere cumSeconds in notazione scientifica non richiede di specificare che il numero è long con L o l alla fine.

+2

31557600000 è il numero di millisecondi in un anno medio nel calendario * Julian *. Per il calendario gregoriano, è 31556952000. – dan04

+34

Va bene, lo dirò. Questo non è un buon nome variabile. – isanae

+0

Nel caso in cui fare questo senza un loop ti interessa; è possibile utilizzare la divisione: 'System.out.println (1970 + (long) Math.ceil (Long.MAX_VALUE/31557600000L));' – Paulpro

risposta

41

Il motivo che fa la differenza è perché il numero notazione scientifica 3.1558E+10 è un double letterale, mentre il letterale 31558000000L è naturalmente un long letterale.

Questo fa la differenza in the += operator.

Un'espressione assegnazione composta del op modulo E1 = E2 è equivalente a E1 = (T) ((E1) op (E2)), dove T è il tipo di E1, tranne che E1 viene valutato solo una volta .

Fondamentalmente, lungo + = lungo produce un lungo, ma lungo + = doppio produce anche un lungo.

Quando si aggiunge uno double, il valore iniziale di cumSeconds viene allargato a double e quindi si verifica l'aggiunta. Il risultato viene sottoposto a narrowing primitive conversion a long.

Una conversione ambito di un numero a virgola mobile a un tipo integrale T assume due fasi:

  1. Nella prima fase, il numero in virgola mobile è convertita in un lungo, se T è lungo

(snip)

  • Altrimenti, uno dei due casi seguenti devono essere vere:

    • Il valore deve essere troppo piccola (un valore negativo di grandi dimensioni o infinito negativo), e il risultato della prima fase è il più piccolo rappresentabile valore di tipo int o long.

    • Il valore deve essere troppo grande (un valore positivo di grandi dimensioni o infinito positivo), e il risultato della prima fase è la grande valore rappresentabile di int long.

(grassetto corsivo mio)

Il risultato casualmente è troppo grande per essere rappresentato in un long, per cui il risultato si restringe a Long.MAX_VALUE, e il ciclo termina while.

Tuttavia, quando si utilizza un valore letterale long, si aggiunge continuamente un valore pari a un valore pari, che alla fine si sovrapporrà. Questo non imposta il valore su Long.MAX_VALUE, che è dispari, quindi il ciclo è infinito.

Ma invece di fare affidamento su un'aggiunta che alla fine produce Long.MAX_VALUE, con Java 1.8+ è possibile testare esplicitamente l'overflow con Math.addExact.

Restituisce la somma dei suoi argomenti, generando un'eccezione se il risultato trabocca a lungo.

Produce:

ArithmeticException - se il risultato trabocca una lunga

+8

Nota: questo significa anche che 'x + = 0.0' o anche' x + = 0.0f' può causare una lunga perdita di precisione. –

+1

Eventuali puntatori a utilizzare 'Math.addExact()' o qualcosa per rilevare un overflow? –

6

L'osservazione chiave è che cumSeconds < Long.MAX_VALUE dove cumSeconds è un long può essere falso solo se cumSeconds è esattamente Long.MAX_VALUE.

Se si esegue il calcolo con numeri lunghi, è necessario un po 'di tempo per raggiungere esattamente questo valore (se mai raggiunto) perché l'aritmetica lunga si avvolge quando si esce dall'intervallo numerico.

Se si esegue l'aritmetica con numeri doppi, verrà generato il valore massimo quando il valore doppio è sufficientemente grande.

5

@rgettman è già entrato nei dettagli della ginnastica rotonda che si svolge quando si utilizza uno double anziché uno long. Ma c'è di più.

Quando si aggiunge ripetutamente un numero elevato a long, alla fine si otterrà un risultato negativo. Ad esempio, Long.MAX_VALUE + 1L = Long.MIN_VALUE. Quando ciò accade, dovrai semplicemente ripetere il processo indefinitamente.

Quindi, se hai cambiato il codice per:

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

ti cattura in cui le cose vanno negativo perché cumSeconds rotolato sopra.

Problemi correlati