2012-10-26 20 views
7

Voglio capire come il tipo Java double memorizzerà il suo valore in memoria in Java. Quando eseguo il seguente codice ottengo uscita inaspettata:Comportamento imprevisto di dati di tipo a doppia primitiva

public static void main(String[] args) { 

    float a = 1.5f; 
    float b= 0.5f; 
    double c= 1.5; 
    double d = 0.5; 

    float a1 = 1.4f; 
    float b1= 0.5f; 
    double c1= 1.4; 
    double d1 = 0.5; 

    System.out.println(" a- b is " + (a-b)); 
    System.out.println(" c- d is " + (c-d)); 
    System.out.println("a1-b1 is " + (a1-b1)); 
    System.out.println("c1-d1 is " + (c1-d1)); 

}

uscita:

 
a- b is 1.0 
c- d is 1.0 
a1-b1 is 0.9 
c1-d1 is 0.8999999999999999 

Perché c1-d1 non è uguale a 0.9?

Ho anche provato altri valori diversi ma un po 'di tempo restituisce il risultato previsto e un po' di tempo no.

+4

Simile a http://stackoverflow.com/questions/322749/retain-precision-with-doubles-in-java – tjg184

+1

Questo può aiutare http: //epramono.blogspot .com/2005/01/double-vs-bigdecimal.html – kosa

+0

Vedere anche http://mindprod.com/jgloss/floatingpoint.html –

risposta

3

Mentre potresti aver sentito parlare di errori di arrotondamento, potresti chiederti perché hai un errore di arrotondamento qui.

float a1 = 1.4f; 
float b1 = 0.5f; 
double c1 = 1.4; 
double d1 = 0.5; 

System.out.println(new BigDecimal(a1) + " - " + new BigDecimal(b1) + " is " + 
     new BigDecimal(a1).subtract(new BigDecimal(b1)) + " or as a float is " + (a1 - b1)); 
System.out.println(new BigDecimal(c1) + " - " + new BigDecimal(d1) + " is " + 
     new BigDecimal(c1).subtract(new BigDecimal(d1)) + " or as a double is " + (c1 - d1)); 

stampe

1.39999997615814208984375 - 0.5 is 0.89999997615814208984375 or as a float is 0.9 
1.399999999999999911182158029987476766109466552734375 - 0.5 is 
    0.899999999999999911182158029987476766109466552734375 
    or as a double is 0.8999999999999999 

Come si può vedere, né floatdouble possono rappresentare questi valori esattamente, e quando il galleggiante o doppio viene stampato, un po 'l'arrotondamento avviene per nascondere questo da voi. In questo caso di float, l'arrotondamento a 7 cifre decimali produce il numero che ci si aspettava. Nel caso di double che ha 16 cifre di precisione, l'errore di arrotondamento è visibile.

Come @Eric Postpischil, indica se l'operazione float o double ha un errore di arrotondamento dipende interamente dai valori utilizzati. In questa situazione, era il galleggiante che sembrava essere più preciso anche se il valore rappresentato era più lontano da 0,9 rispetto al doppio valore.

In breve: se si intende utilizzare float o double, è necessario utilizzare una strategia di arrotondamento ragionevole. Se non puoi farlo, usa BigDecimal.

System.out.printf("a1 - b1 is %.2f%n", (a1 - b1)); 
System.out.printf("c1 - d1 is %.2f%n", (c1 - d1)); 

stampe

a1 - b1 is 0.90 
c1 - d1 is 0.90 

Quando si stampa un float o doppio, si assume che il valore decimale breve più vicino è quello che si vuole veramente. cioè entro 0,5 ulp.

E.g.

double d = 1.4 - 0.5; 
float f = d; 
System.out.println("d = " + d + " f = " + f); 

stampe

d = 0.8999999999999999 f = 0.9 
+2

La frase "Nel caso del doppio che ha 16 cifre di precisione, l'errore di arrotondamento è visibile" suggerisce che il doppio valore è stato visualizzato con più cifre perché il doppio ha maggiore precisione. Questo non è il caso. Per ogni tipo, float e double, se il risultato della sottrazione .5 è anche il valore rappresentabile più vicino al risultato esatto (quando vengono utilizzati i numeri originali) è effettivamente successo. È una funzione di ciò che i bit accadono per seguire la posizione in cui avviene l'arrotondamento, non una funzione di quanti bit vengono utilizzati. Ad esempio, prova "1.2f - .5f" e "1.2 - .5". –

+0

@EricPostpischil Grazie. Come è stato scritto, non era chiaro cosa intendessi. –

1

La documentazione Java per println fa riferimento (tramite diversi collegamenti) alla documentazione per toString. Lo documentation for toString indica che il numero di cifre stampate per float o double è il numero necessario per distinguere in modo univoco il valore da valori rappresentativi adiacenti nello stesso tipo.

Quando "1.4f" viene convertito in float, il risultato è 1.39999997615814208984375 (in virgola mobile esadecimale, 0x1.666666p + 0). Quando viene sottratto .5, il risultato è 0.89999997615814208984375 (0x1.ccccccp-1). Come succede, questo float è anche il float più vicino a .9. Quindi, quando viene stampato, viene stampato ".9".

Quando "1,4" viene convertito in doppio, il risultato è 1,399999999999999911182158029987476766109466552734375 (0x1,66666666666pp + 0). Quando viene sottratto .5, il risultato è 0.899999999999999911182158029987476766109466552734375 (0x1.cccccccccccccp-1). Questo è non il doppio che è più vicino a .9, come 0.9000000000000000002220446049250313080847263336181640625 (0x1.ccccccccccccdp-1) è più vicino. Pertanto, quando viene stampato, le specifiche Java richiedono che il valore venga stampato più finemente, distinguendolo da .9. Il risultato, "0.8999999999999999", rappresenta esattamente il valore effettivo.

Problemi correlati