ArithmeticException generata durante BigDecimal.divide

Pensavo che java.math.BigDecimal avrebbe dovuto essere The Answer ™ per la necessità di eseguire aritmetica di precisione infinita con numeri decimali.

Considera il seguente frammento:

 import java.math.BigDecimal; //... final BigDecimal one = BigDecimal.ONE; final BigDecimal three = BigDecimal.valueOf(3); final BigDecimal third = one.divide(three); assert third.multiply(three).equals(one); // this should pass, right? 

Mi aspetto che l’ assert passi, ma in realtà l’esecuzione non arriva nemmeno lì: one.divide(three) fa sì che venga lanciata ArithmeticException !

 Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result. at java.math.BigDecimal.divide 

Si scopre che questo comportamento è esplicitamente documentato nell’API :

Nel caso della divide , il quoziente esatto potrebbe avere un’espansione decimale infinitamente lunga; ad esempio, 1 diviso per 3. Se il quoziente presenta un’espansione decimale senza terminazione e l’operazione viene specificata per restituire un risultato esatto, viene generata ArithmeticException . In caso contrario, viene restituito il risultato esatto della divisione, come fatto per altre operazioni.

    Navigando intorno all’API, si scopre che in effetti esistono diversi sovraccarichi di divide che eseguono una divisione inesatta , ovvero:

     final BigDecimal third = one.divide(three, 33, RoundingMode.DOWN); System.out.println(three.multiply(third)); // prints "0.999999999999999999999999999999999" 

    Certo, la domanda ovvia ora è “Qual è il punto ???”. Ho pensato che BigDecimal è la soluzione quando abbiamo bisogno di calcoli matematici esatti , ad esempio per calcoli finanziari. Se non possiamo nemmeno divide esattamente, allora quanto può essere utile? In realtà ha uno scopo generale o è utile solo in applicazioni di nicchia in cui per fortuna non hai proprio bisogno di divide ?

    Se questa non è la risposta giusta, cosa possiamo usare per la divisione esatta nel calcolo finanziario? (Voglio dire, non ho una specializzazione in finanza, ma usano ancora la divisione, giusto ???).

    Se questa non è la risposta giusta, cosa possiamo usare per la divisione esatta nel calcolo finanziario? (Voglio dire, non ho una specializzazione in finanza, ma usano ancora la divisione, giusto ???).

    Poi ero nella scuola primaria 1 , mi hanno insegnato che quando dividi per 1 per 3 ottieni uno 0.33333 … cioè un decimale ricorrente . La divisione dei numeri rappresentati in forma decimale NON è esatta. Infatti per ogni base fissa ci saranno frazioni (il risultato della divisione di un intero per un altro) che non possono essere rappresentate esattamente come un numero in virgola mobile di precisione finita in quella base. (Il numero avrà una parte ricorrente …)

    Quando esegui calcoli finanziari che riguardano la divisione, devi considerare cosa fare con una frazione ricorrente. Puoi arrotondarlo o ridurlo, o al numero intero più vicino o qualcos’altro, ma in fondo non puoi semplicemente dimenticarti del problema.

    Il javadoc di BigDecimal dice questo:

    La class BigDecimal offre all’utente il controllo completo sul comportamento di arrotondamento. Se non è specificata alcuna modalità di arrotondamento e il risultato esatto non può essere rappresentato, viene generata un’eccezione; in caso contrario, è ansible eseguire calcoli sulla precisione e sulla modalità di arrotondamento prescelti fornendo all’operazione un object MathContext appropriato.

    In altre parole, è tua responsabilità dire a BigDecimal cosa fare per arrotondare.

    EDIT – in risposta a questi follow-up dall’OP.

    In che modo BigDecimal rileva infiniti decimali ricorrenti?

    Non rileva esplicitamente il decimale ricorrente. Rileva semplicemente che il risultato di qualche operazione non può essere rappresentato esattamente usando la precisione specificata; ad es. sono necessarie troppe cifre dopo il punto decimale per una rappresentazione esatta.

    Deve tenere traccia di e rilevare un ciclo nel dividendo. POTREBBE aver scelto di gestire questo altro modo, contrassegnando dove è la parte ricorrente, ecc.

    Suppongo che BigDecimal possa essere stato specificato per rappresentare esattamente un decimale ricorrente; cioè come una class BigRational . Tuttavia, ciò renderebbe l’implementazione più complicata e più costosa da utilizzare 2 . E poiché la maggior parte delle persone si aspetta che i numeri vengano visualizzati in decimali e il problema dei decimali ricorrenti si ripresenta in quel punto.

    La linea di fondo è che questa complessità extra e i costi di runtime non sarebbero appropriati per i casi d’uso tipici di BigDecimal . Questo include calcoli finanziari, in cui le convenzioni contabili non consentono di utilizzare decimali ricorrenti.


    1 – Era un’ottima scuola elementare …

    2 – O si tenta di rimuovere i fattori comuni del divisore e del dividendo (computazionalmente costosi), o di consentire loro di crescere senza limiti (costosi nell’uso dello spazio … e computazionalmente per operazioni successive).

    La class è BigDecimal non BigFractional . Da alcuni dei tuoi commenti sembra che tu voglia solo lamentarti del fatto che qualcuno non ha costruito in tutti i possibili algoritmi di gestione dei numeri in questa class. Le app finanziarie non hanno bisogno di precisione decimale infinita; solo valori perfettamente accurati alla precisione richiesta (in genere 0, 2, 4 o 5 cifre decimali).

    In realtà mi sono occupato di molte applicazioni finanziarie che usano il double . Non mi piace, ma era così che sono scritti (nemmeno in Java). Quando ci sono tassi di cambio e conversioni di unità, allora ci sono sia il potenziale di arrotondamento che di lividi. BigDecimal elimina il dopo ma c’è ancora il primo per la divisione.

    Se vuoi lavorare con i decimali, non con i numeri razionali, e hai bisogno dell’aritmetica esatta prima dell’arrotondamento finale (arrotondando per centesimi o qualcosa), ecco un piccolo trucco.

    Puoi sempre manipolare le tue formule in modo che ci sia solo una divisione finale. In questo modo non perderai la precisione durante i calcoli e otterrai sempre il risultato correttamente arrotondato. Per esempio

     a/b + c 

    equivale

     (a + bc) / b. 

    A proposito, apprezzerei davvero le opinioni di persone che hanno lavorato con il software finanziario. Ho sentito spesso BigDecimal essere sostenuto più del doppio

    Nei report finanziari utilizziamo alwasy BigDecimal con scale = 2 e ROUND_HALF_UP , poiché tutti i valori stampati in un report devono portare a un risultato riproducibile. Se qualcuno lo controlla usando una semplice calcolatrice.

    In Svizzera arrotondano a 0,05 poiché non hanno più monete da 1 o 2 Rappen .

    C’è bisogno di

     a=1/3; b=a*3; resulting in b==1; 

    nei sistemi finanziari? Non credo. Nei sistemi finanziari è definito, quale roundmode e scala devono essere usati, quando si fanno i calcoli. In alcune situazioni, la modalità round e la scala sono definite nella legge. Tutti i componenti possono fare affidamento su un comportamento definito. Restituire b == 1 sarebbe un errore, perché non soddisferebbe il comportamento specificato. Questo è molto importante quando si calcolano i prezzi, ecc.

    È come le specifiche IEEE 754 per rappresentare i float in cifre binarie. Un componente non deve ottimizzare una rappresentazione “migliore” senza perdita di informazioni, perché ciò interromperà il contratto.

    Si dovrebbe preferire BigDecimal per i calcoli di finanza. L’arrotondamento dovrebbe essere specificato dall’azienda. Ad esempio un importo (100,00 $) deve essere diviso in parti uguali su tre conti. Ci deve essere una regola di business che prende il centesimo in più.

    Doppio, i float non sono approriati per l’uso in applicazioni finanziarie perché non possono rappresentare frazioni di 1 precisamente che non sono esponenziali di 2. Ad esempio, considerare 0.6 = 6/10 = 1 * 1/2 + 0 * 1/4 + 0 * 1 / 8 + 1 * 1/16 + … = 0,1001 … b

    Per i calcoli matematici è ansible utilizzare un numero simbolico, ad es. Memorizzare denominatore e numeratore o anche un’espressione intera (ad es. Questo numero è sqrt (5) +3/4). Dato che questo non è il caso d’uso principale della java api, hai vinto “trovalo lì”.

    Per dividere save, devi impostare MATHcontext ,

    BigDecimal bd = new BigDecimal(12.12, MathContext.DECIMAL32).divide(new BigDecimal(2)).setScale(2, RoundingMode.HALF_UP);

    Accetto che Java non abbia un grande supporto per rappresentare le frazioni, ma devi capire che è imansible mantenere le cose completamente precise quando si lavora con i computer. Almeno in questo caso, l’eccezione sta dicendo che la precisione si sta perdendo.

    Per quanto ne so, “aritmetica di precisione infinita con numeri decimali” non sta per accadere. Se devi lavorare con i decimali, probabilmente quello che stai facendo va bene, prendi le eccezioni. Altrimenti, una rapida ricerca su google trova alcune risorse interessanti per lavorare con le frazioni in Java:

    http://commons.apache.org/math/userguide/fraction.html

    http://www.merriampark.com/fractions.htm

    Il modo migliore per rappresentare una frazione in Java?

    Si noti che stiamo usando un computer … Un computer ha un sacco di RAM e la precisione richiede RAM. Quindi quando vuoi una precisione infinita di cui hai bisogno
    (infinite * infinite) ^ (infinite * Integer.MAX_VALUE) terrabyte ram …

    So che 0.333333... è 0.333333... e dovrebbe essere ansible archiviarlo in ram come “uno diviso per tre” e quindi puoi moltiplicarlo e dovresti avere 1 . Ma non penso che Java abbia qualcosa del genere …
    Forse devi vincere il Premio Nobel per aver scritto qualcosa che lo ha fatto. ;-)