2012-03-20 18 views
13

so il seguente comportamento è un vecchio problema, ma ancora non capisco.Java BigDecimal problemi di precisione

System.out.println(0.1 + 0.1 + 0.1);  

O anche se io uso BigDecimal

System.out.println(new BigDecimal(0.1).doubleValue() 
    + new BigDecimal(0.1).doubleValue() 
    + new BigDecimal(0.1).doubleValue()); 

Perché questo risultato è: 0.30000000000000004 invece di: 0.3?

Come posso risolvere questo?

+12

Tu ... non si utilizza 'BigDecimal'. Stai facendo * esattamente la stessa cosa * di aggiungere i doppi. 'doubleValue()' restituisce ... un doppio. Vedi il Javadoc per 'BigDecimal' su come aggiungere/sottrarre/etc –

+0

Con i calcoli' double', vedi [Cosa ogni scienziato informatico dovrebbe sapere circa l'aritmetica virgola mobile] (http://docs.oracle.com/cd/ E19957-01/806-3568/ncg_goldberg.html). Java risolve questo fornendo opzioni di formato per l'output. –

+0

Perché stai usando 'doubleValue()' ??? Pensavo volessi un tipo decimale. –

risposta

26

quello che realmente vuole è

new BigDecimal("0.1") 
.add(new BigDecimal("0.1")) 
.add(new BigDecimal("0.1")); 

Il costruttore new BigDecimal(double) ottiene tutto l'imprecisione del double, quindi per il momento che hai detto 0.1, hai già introdotto l'errore di arrotondamento. Utilizzando il costruttore String evita l'errore di arrotondamento associati con l'andare via double.

+0

grazie per tutti! Ora ... posso capirlo. ' // FUNZIONA PER IL MIO PROBLEMA Valore doppio = 0,1 d; BigDecimal totale = new BigDecimal ("0.0"); per (int i = 0; i <9; i ++) { \t total = total.add (new BigDecimal (value.toString())); } System.out.print (totale); \t // NON FUNZIONA System.out.print (0,1d + 0,1d + 0,1d); // NON FUNZIONA System.out.println (new BigDecimal (0.1) .add (new BigDecimal (0.1)). Add (new BigDecimal (0.1))); // FUNZIONA System.out.println (new BigDecimal ("0.1"). Add (new BigDecimal ("0.1")). Add (new BigDecimal ("0.1"))); // FUNZIONA, MA CON MENO PRECISIONE System.out.println (0.1f + 0.1f + 0.1f); ' – Jefferson

+0

Una leggera correzione: _" imprecisione del doppio "_ - questo non è tecnicamente vero. Il doppio può rappresentare perfettamente '0.1' - con una mantissa di 1 e un esponente di -1. È la conversione di un 'double' in un' BigDecimal' che converte la rappresentazione in virgola mobile in una normale rappresentazione binaria lungo la strada. Questo è il motivo per cui il doppio costruttore è difettoso. – theeggman85

+2

Ehm, cosa? No, non può. '0.1' non può essere rappresentato come una frazione in binario, punto, che è il modo in cui' double's sono rappresentati. Una mantissa di 1 e un esponente di -1 ti danno 0.5. –

3

Prova questo:

BigDecimal sum = new BigDecimal(0.1).add(new BigDecimal(0.1)).add(new BigDecimal(0.1)); 

EDIT: In realtà, guardando oltre il Javadoc, questo avrà lo stesso problema come l'originale. Il costruttore BigDecimal(double) farà un BigDecimal corrispondente alla rappresentazione in virgola mobile esatto di 0,1, che non è esattamente uguale a 0,1.

Questo, tuttavia, dà il risultato esatto, dal momento che i numeri interi possono sempre essere espressi esattamente nella rappresentazione in virgola mobile:

BigDecimal one = new BigDecimal(1); 
BigDecimal oneTenth = one.divide(new BigDecimal(10)); 

BigDecimal sum = oneTenth.add(oneTenth).add(oneTenth); 
+5

MAI usare il costruttore 'double' per grandi numeri decimali (beh, potrebbero esserci alcune situazioni rare, ma in realtà è una cattiva idea). Se puoi, usa il costruttore di stringhe (che sarà esatto), se hai già un doppio valore 'valueOf' in questo modo non otteniamo ulteriore precisione fasulla ... – Voo

3

Questo non è un problema di Java, ma piuttosto un problema di computer in generale. Il problema principale sta nella conversione dal formato decimale (formato umano) al formato binario (formato computer). Alcuni numeri in formato decimale non sono rappresentabili in formato binario senza decimali a ripetizione infinita.

Ad esempio, 0.3 decimale è 0.01001100 ... binario Ma un computer ha un "slot" limitato (bit) per salvare un numero, quindi non può salvare tutta la rappresentazione infinita. Salva solo 0.01001100110011001100 (ad esempio). Ma quel numero in decimale non è più 0,3, ma 0,30000000000000004.

+0

http://en.wikipedia.org/ wiki/Binary-coded_decimal –

8

Prima mai, non utilizzare mai il doppio costruttore di BigDecimal. Può essere la cosa giusta in alcune situazioni, ma per lo più non

è se è possibile controllare usare il tuo ingresso il costruttore BigDecimal String come è stato già proposto. In questo modo ottieni esattamente quello che vuoi. Se si dispone già di una doppia (può capitare, dopo tutto), non utilizzare la doppia funzione di costruzione, ma invece il metodo statico valueOf. Questo ha il vantaggio di ottenere la rappresentazione cannone del doppio che attenua il problema almeno .. e il risultato è di solito molto più intuitivo.

2

Il problema che ho è che 0,1 è rappresentato con un numero leggermente superiore ad esempio

System.out.println(new BigDecimal(0.1)); 

stampe

0.1000000000000000055511151231257827021181583404541015625 

The Double.toString() prende in considerazione questo errore di rappresentazione in modo da non vederlo.

Analogamente, 0,3 è rappresentato da un valore leggermente inferiore a quello che è realmente.

0.299999999999999988897769753748434595763683319091796875 

Se si moltiplica il valore rappresentato da 0,1 da 3 non si ottiene il valore rappresentato per 0,3, voi invece ottiene qualcosa di un po 'più alto

0.3000000000000000166533453693773481063544750213623046875 

Questo non è solo un errore di rappresentazione, ma anche un errore di arrotondamento causato dalle operazioni. Questo è più che Double.toString() si correggerà e quindi vedrai l'errore di arrotondamento.

La morale della storia, se si utilizza float o double, arrotondare la soluzione in modo appropriato.

double d = 0.1 + 0.1 + 0.1; 
System.out.println(d); 
double d2 = (long)(d * 1e6 + 0.5)/1e6; // round to 6 decimal places. 
System.out.println(d2); 

stampe

0.30000000000000004 
0.3