2014-10-11 11 views
10

Sto chiamando un metodo troncato per troncare il valore doppio in modo che ci debba essere una singola cifra dopo il decimale (senza arrotondamento),
Per es. truncate(123.48574) = 123.4.Perché questo output strano con troncato e BigDecimal?

Il mio metodo Truncate è qualcosa di simile

public double truncate (double x) { 
    long y = (long) (x * 10); 
    double z = (double) (y/10); 
    return z; 
} 

suo lavorando bene per quasi tutti i valori ad eccezione di questa uscita strano.

double d = 0.787456; 
d = truncate(d + 0.1); //gives 0.8 as expected. Okay. 

Ma,

double d = 0.7; 
d = truncate(d + 0.1); //should also give 0.8. But its giving 0.7 only. 
         //Strange I don't know why? 

Infatti funziona bene per tutti gli altri 0,1, 0,2, 0,3, 0,4, 0,5, 0,6, -, 0.8, 0.9
Cioe per esempio,

double d = 0.8; 
d = truncate(d + 0.1); //gives 0.9 as expected 

Anch'io l'ho provato con BigDecimal. Ma lo stesso Nessun cambiamento. Ecco il codice per questo.

double d = 0.7; 
BigDecimal a = new BigDecimal(d + 0.1); 
BigDecimal floored = a.setScale(1, BigDecimal.ROUND_DOWN); 
double d1 = floored.doubleValue(); 
System.out.println(d1); //Still gives 0.7 

E ancora il fatto vero è che funziona bene con Math.round.

public double roundUp1d (double d) { 
    return Math.round(d * 10.0)/10.0; 
} 

Quindi, se io chiamo roundUp1d(0.7 + 0.1), dà 0,8 come previsto. Ma non voglio che i valori siano arrotondati, quindi non posso usarlo.

Qual è il problema con 0,7?

+1

L'ho provato, Se il tuo input è 0,7 + 0,1, allora sarà calcolato male a 0,79999999999999999 – maskacovnik

+0

Ma questo non accade con tutti gli altri doppi - 0.1, 0.2 ..etc – user1612078

+0

I valori doppi sono imprecisi. Dovresti invece pensare a usare BigDecimal. – Tom

risposta

3

I punti in virgola mobile sono intrinsecamente imprecisi dal loro design. Altre risposte qui spiegano già la teoria alla base di questa inesattezza. Si consiglia vivamente di utilizzare BigDecimal e BigInteger.

Nella mia risposta voglio approfondire come si sta usando BigDecimal in modo errato e come si può usare correttamente. Non commettere l'errore di utilizzare semplicemente queste classi come wrapper per i calcoli in virgola mobile. Nel codice attuale:

BigDecimal a = new BigDecimal(d + 0.1); 

Anche se si sta tentando di utilizzare BigDecimal qui, si sta ancora eseguendo l'aggiunta usando i calcoli in virgola mobile regolari. Questo è esattamente lo stesso di fare:

double d_a = d + 0.1; //0.799999999 ad infinitum 
BigDecimal a = new BigDecimal(d_a); 

Per sfruttare la precisione dei BigX classi, è necessario utilizzare i propri metodi di calcolo, così come il metodo valueOf statico (non il costruttore):

BigDecimal a = BigDecimal.valueOf(d).add(BigDecimal.valueOf(0.1)); 

Qui, due BigDecimal oggetti vengono creati in modo che corrisponda esattamente 0,7 e 0,1, allora il metodo add viene utilizzato per calcolare la loro somma e produrre una terza BigDecimal obj ect (che sarà esattamente 0.8).

Utilizzando il metodo statico valueOf anziché costruttore assicura il creato BigDecimal oggetto rappresenta l'esatto valore double come appare quando convertito in una stringa (0,7 come una stringa è "0,7"), piuttosto che il valore approssimativo memorizzato da il computer per rappresentarlo (il computer memorizza 0.7 come 0.699999999 ad infinitum).

4

Questo non è causato dal programma né da Java. Semplicemente, i numeri in virgola mobile sono imprecisi in base alla progettazione. Non puoi sapere quali numeri saranno imprecisi, ma alcuni lo faranno (0.7 nel tuo caso). Uno dei numerosi articoli su questo argomento: http://effbot.org/pyfaq/why-are-floating-point-calculations-so-inaccurate.htm

Bottom line: mai fidarsi che un doppio 0,7 sia VERAMENTE 0.7.

+0

Ma ho provato lo stesso con 'Math.round' e mi ha dato il risultato atteso. Vedi il mio post aggiornato. – user1612078

+0

In questo ultimo tentativo, stai ancora ottenendo '0.699999..' ma' println' arrotonda anche l'output. – usr2564301

+0

Questo è un problema molto comune e non ho mai trovato la soluzione definitiva. Un paio di altri suggerimenti che potrebbero aiutarti: http://mydevelopedworld.wordpress.com/2013/05/19/java-avoid-double-and-float-types-when-accuracy-is-required/ e http:// StackOverflow.it/questions/16133520/how-to-actually-avoid-floating-point-errors-when-you-need-to-use-float – cornuz

2

Java utilizza il formato IEEE 754 per codificare valori doppi.

Ciò significa che ogni numero è un'approssimazione il più precisa possibile.

0.7 è bestly approssimata da 0,699999999999999999999999

Check this out: http://www.binaryconvert.com/result_double.html?decimal=048046055

Per risolvere il problema, si può provare moltiplicando per 10,0 e trattare i valori di conseguenza?

7

(Se non siete interessati in teoria, scorrere fino alla fine, c'è la correzione per il codice)

La ragione è molto semplice: Come sapete, il sistema binario supporta solo 0 s e 1 s

Quindi, diamo un'occhiata a vostri valori, e ciò che essi sono in rappresentazione binaria:

0.1 - 0.0001100110011001100110011001100110011001100110011001101 
0.2 - 0.001100110011001100110011001100110011001100110011001101 
0.3 - 0.010011001100110011001100110011001100110011001100110011 
0.4 - 0.01100110011001100110011001100110011001100110011001101 
0.5 - 0.1 
0.6 - 0.10011001100110011001100110011001100110011001100110011 
0.7 - 0.1011001100110011001100110011001100110011001100110011 
0.8 - 0.1100110011001100110011001100110011001100110011001101 
0.9 - 0.11100110011001100110011001100110011001100110011001101 

che cosa vuol dire? 0.1 è un decimo di 1.Nessun problema nel sistema decimale, basta spostare il separatore di una posizione. Ma in binario il numero di telefono non può essere espresso 0,1 - perché ogni turno del segno decimale equivale a *2 o /2 - a seconda della direzione. (E 10 non può essere diviso in X turni di 2)

Per valori che si desidera dividere per multipli di 2 - si ottiene un risultato esatto:

1/2 - 0.1 
1/4 - 0.01 
1/8 - 0.001 
1/16- 0.0001 
and so on. 

Pertanto cercando di calcolare un /10 è un infinita lungo risultato, che viene troncato quando il valore esaurisce i bit.

Detto questo, si tratta di una limitazione del modo in cui i computer funzionano, che tale valore non può mai essere memorizzato con precisione completa.

Site Nota: Questo "fatto" è stata ignorata la Patriot sistema inducendolo a diventare inutilizzabile dopo alcune ore di tempo di funzionamento, vedere qui: http://sydney.edu.au/engineering/it/~alum/patriot_bug.html


Ma perché funziona per ogni cosa tranne 0.7 + 0.1 - si potrebbe chiedere

Se si verifica il codice con 0.8 - funziona - ma non con 0.7 + 0.1.

Ancora, in binario entrambi i valori sono già imprecisi. Se si somma entrambi i valori, il risultato è ancora più impreciso, portando ad un risultato sbagliato:

Se si somma 0,7 e 0,1 (dopo il separatore decimale) si ottiene questo:

0.101100110011001100110011001100110011001100110011001 1000 
+ 0.000110011001100110011001100110011001100110011001100 1101 
    --------------------------------------------------------- 
    0.110011001100110011001100110011001100110011001100110 0101  

Ma 0.8 sarebbe

0.110011001100110011001100110011001100110011001100110 1000 

Confronta gli ultimi 4 bit e la nota, che il conseguente "0.8" della somma è minore se si desidera convertire 0.8 in binario direttamente.

Indovinate un po ':

System.out.println(0.7 + 0.1 == 0.8); //returns false 

Quando si lavora con i numeri si dovrebbe impostare voi stessi un limite di precisione - e numeri SEMPRE rotonde di conseguenza per evitare tali errori (non troncare!):

//compare doubles with 3 decimals 
System.out.println((lim(0.7, 3) + lim(0.1, 3)) == lim(0.8, 3)); //true 

public static long lim(double d, int t){ 
    return Math.round(d*10*t); 
} 

Per avere il codice corretto: arrotondarlo a 4 cifre, prima di troncare dopo la prima cifra:

public static double truncate(double x){ 
    long y = (long)((Math.round(x*10000)/10000.0)*10); 
    double z = (double)y/10; 
    return z; 
} 

System.out.println(truncate(0.7+0.1)); //0.8 
System.out.println(truncate(0.8)); //0.8 

Questo troncerà ancora come desiderato ma garantisce che un sarà arrotondato a 0.7 prima di troncarlo. È possibile impostare la precisione richiesta per l'applicazione. 10, 20, 30, 40 cifre?

Altri valori rimarranno corretti, perché qualcosa come 0.58999 sarà arrotondato a 0.59 - così ancora troncati come 0.5 e non arrotondato a 0.6

+0

il codice fisso funzionerà per tutte queste anomalie dei numeri mobili? Perché se può accadere con 0.7, può accadere con qualsiasi numero variabile arbitrario. Intendo il suo imprevedibile. – user1612078

+0

@ user1612078 Risolverà il problema per TUTTI i numeri in base alla precisione scelta. Se si decide di ignorare qualcosa di 20 posti dietro il separatore decimale, questo produrrà risultati errati, se un numero differisce da un altro a partire da 21 punti dietro il separatore. – dognose

3

Il 'problema' spetta utilizzando numero decimale come double o float. Questi numeri usano internamente 2 frazioni per approssimare una vasta gamma di numeri, dal molto piccolo al molto grande.

Quindi, ad esempio, può essere rappresentato come 1/2 e 0.75 come 1\2 + 1\4.

Tuttavia, come hai scoperto, non è sempre possibile convertire facilmente tra frazioni di base 10 e frazioni di base 2.

Dove nella base 10 0.7 è uguale a 7/10, in una frazione di base 2 diventa molto difficile.

Questo è come provare a rappresentare con precisione 1/3 in un numero decimale base 10, che è molto facile in una frazione di base 3, si può ottenere un'approssimazione molto vicina, purché si abbiano abbastanza posizioni decimali disponibili, tuttavia non è possibile Rappresentare con precisione 1/3 in un numero decimale base 10.

3

Il punto chiave è che il galleggiante e il doppio sono progettati per funzionare con una filosofia di arrotondamento. Se il risultato di un calcolo non può essere rappresentato esattamente, il risultato sarà il più vicino possibile alla risposta esatta, indipendentemente dal fatto che lo renda più piccolo o più grande dell'esatto.

Il problema con 0.7 + 0.1 è 0.7999999999999999993338661852249060757458209991455078125, il valore rappresentativo più vicino alla somma di 0,699999999999999999555910790149937383830547332763671875 e 0,1000000000000000055511151231257827021181583404541015625, i numeri rappresentativi più vicini a 0,7 e 0,1, è leggermente inferiore a 0,8.

Ci sono un paio di possibili soluzioni. Se il decimale del troncamento è fondamentale per il problema e più importante delle prestazioni e dello spazio, utilizzare BigDecimal. In caso contrario, prendere in considerazione l'aggiunta di un piccolo aggiustamento per tenere conto di questo effetto prima di troncare. In effetti, trattano numeri leggermente inferiori a 0,8 come maggiori o uguali a 0,8. Questo può funzionare, perché le differenze introdotte dall'aritmetica double sono in genere molto più piccole delle differenze che si presentano e contano nel mondo reale.

Problemi correlati