2014-10-19 9 views
21

Al lavoro, abbiamo riscontrato un problema durante il tentativo di dividere un numero elevato per 1000. Questo numero proviene dal database.Java's Bigdecimal.divide and rounding

Dire che ho questo metodo:

private static BigDecimal divideBy1000(BigDecimal dividendo) { 
    if (dividendo == null) return null; 

    return dividendo.divide(BigDecimal.valueOf(1000), RoundingMode.HALF_UP); 
} 

Quando faccio la seguente chiamata

divideBy1000(new BigDecimal("176100000")) 

ricevo il valore atteso di 176100. Ma se provo la linea sotto

divideBy1000(new BigDecimal("1761e+5")) 

Ricevo il valore 200000. Perché questo si verifica? Entrambi i numeri sono gli stessi con una rappresentazione diversa e l'ultimo è quello che ricevo dal database. Capisco che, in qualche modo, la JVM stia dividendo il numero 1761 per 1000, arrotondando e riempiendo di zero alla fine.

Qual è il modo migliore per evitare questo tipo di comportamento? Tieni presente che il numero originale non è controllato da me.

+0

Perché si ottiene un BigDecimal dal database come stringa piuttosto che come BigDecimal in primo luogo? http://docs.oracle.com/javase/7/docs/api/java/sql/ResultSet.html#getBigDecimal%28int%29 –

+1

Non lo so. Questo è stato il mio modo di semplificare l'esempio qui. Il codice originale utilizza il metodo getBigDecimal. –

+5

Allora, dove nel tuo codice reale hai il problema di trasformare la stringa "1761e + 5" in un BigDecimal? Qual è il problema * attuale * nel tuo codice * effettivo *? –

risposta

18

Come specificato in javadoc, un BigDecimal è definito da un valore intero e una scala

Il valore del numero rappresentato dalla BigDecimal è quindi (unscaledValue × 10^(- scala)).

Così BigDecimal("1761e+5") ha scalare -5 e BigDecimal(176100000) ha scalare 0.

la divisione del due BigDecimal vengono eseguiti utilizzando rispettivamente le scale -5 e 0 perché le scale non vengono specificate durante la divisione. Il divide documentation spiega perché i risultati sono diversi.

divide

public BigDecimal divide(BigDecimal divisor) 

Ritorna un BigDecimal cui valore è (this/divisor), e la cui scala è preferito (this.scale() - divisor.scale()); se il quoziente esatto non può essere rappresentato (poiché ha un'espansione decimale senza terminazione) viene lanciato un valore ArithmeticException.

Parametri:

divisor - valore per il quale questo BigDecimal deve essere divisa.

Returns:

this/divisor 

getta:

ArithmeticException - se il quoziente esatto non dispone di un'espansione di terminazione decimale

dal:

1.5

Se si specifica una scala al momento della divisione, ad es. dividendo.divide(BigDecimal.valueOf(1000), 0, RoundingMode.HALF_UP) otterrete lo stesso risultato.

+0

Ero nel mezzo della digitazione, ma volevo triplicare i miei dati. +1, questa è la risposta giusta. – Makoto

+0

Grazie! Ho provato qui nel mio "progetto giocattolo" e ho lavorato. Era esattamente la risposta che volevo. –

0

Sì, è una specie di problema quello che stai sperimentando. Se posso, in una situazione in cui hai solo numeri di exponental, dovresti scriverli e quindi usare il tuo metodo. Vedere ciò che suggerisco è questo pezzo di codice di laggiù:

long longValue = Double.valueOf("1761e+5").longValue(); 
BigDecimal value= new BigDecimal(longValue); 

utilizzarlo in un metodo che potrebbe convertire coloro stringa in una nuova BigDecimal e restituire questo valore BigDecimal. Quindi puoi utilizzare quei valori restituiti con divideBy1000. Questo dovrebbe risolvere qualsiasi problema tu stia riscontrando.

Se si dispone di un sacco di quelli, cosa si può fare anche in serbo quelli BigDecimal in una struttura dati come una lista. Quindi utilizzare un ciclo foreach in cui si applica divideBy1000 e ogni nuovo valore verrà memorizzato in un elenco diverso. Quindi dovresti solo accedere a questa lista per avere il tuo nuovo set di valori!

Speranza che aiuta :)

+0

Questo dovrebbe dare quello che stai cercando penso @RaphaeldoVale –

5

Le espressioni new BigDecimal("176100000") e new BigDecimal("1761e+5") sono non uguale. BigDecimal tiene traccia sia del valore sia della precisione.

BigDecimal("176100000") ha 9 cifre di precisione ed è rappresentato internamente come il BigInteger("176100000"), moltiplicato per 1. BigDecimal("1761e+5") ha 4 cifre di precisione ed è rappresentato internamente come il BigInteger("1761"), moltiplicato per 100000.

Quando un divario una BigDecimal per un valore, il risultato rispetta le cifre di precisione, risultando in output diversi per valori apparentemente uguali.

+0

Non è vero. Usa 'compareTo' per vedere che si confrontano in modo equivalente. Inoltre, [Wolfram Alpha] (http://www.wolframalpha.com/input/?i=176100000+%3D%3D+1761e%2B5) supporta la loro uguaglianza. – Makoto

+4

@Makoto: 'compareTo' e' equals' non sono la stessa operazione per 'BigDecimal'. –

+0

Lo capisco. Ma come posso evitare questo comportamento in quanto il numero con minore precisione proviene dal database? Qual è il modo migliore per dividere un numero proveniente dal database? –

0

Provare a utilizzare round().

private static BigDecimal divideBy1000(BigDecimal dividendo) { 
    if (dividendo == null) return null; 

    return dividendo.divide(BigDecimal.valueOf(1000)).round(new MathContext(4, RoundingMode.HALF_UP)); 
} 

public static void main(String []args){ 
    BigDecimal bigD = new BigDecimal("1761e5"); 
    BigDecimal bigDr = divideBy1000(bigD); 
    System.out.println(bigDr); 
} 

La riga new MathContext(4, RoundingMode.HALF_UP)) restituisce la divisione in 4 posizioni.

Questo produce:

1.761E+5 

che è ciò che si desidera.. (:

+2

Perché uno dovrebbe girare per ottenere ** più ** precisione? –

+0

Non ho fatto downvot. Ma la tua risposta non spiega perché questo sembra funzionare. –

0

Ogni volta che si stanno moltiplicando un BigDecimal per una potenza di 10, in questo caso si stanno moltiplicando da 10 -3, è possibile utilizzare dividendo.scaleByPowerOfTen(power) che modifica solo la scala dell'oggetto BigDecimal e laterale passaggi eventuali problemi di arrotondamento, o almeno li sposta a un calcolo successivo.

Le altre risposte qui trattano il caso più generale di divisione per qualsiasi numero.