2010-10-05 13 views
9

Ho ereditato un progetto in cui gli importi monetari usano il doppio tipo.Raddoppia i miei soldi: il mio framework usa i doppi per gli importi monetari

Peggio ancora, il framework che utilizza e le classi proprie del framework utilizzano il doppio del denaro.

L'ORM del framework gestisce anche il recupero dei valori da (e archiviazione a) il database. Nel database i valori monetari sono di tipo numero (19, 7), ma l'ORM del framework li mappa in doppio.

A corto di bypassare completamente le classi framework e ORM, c'è qualcosa che posso fare per calcolare con precisione i valori monetari?

Modifica: sì, so che BigDecimal deve essere utilizzato. Il problema è che sono strettamente legato a un framework in cui, ad esempio, la classe framework.commerce.pricing.ItemPriceInfo ha membri double mRawTotalPrice; e doppio mListPrice. Il codice dell'applicazione della mia azienda si estende, ad esempio, a ItemPriceInfoClass.

Realisticamente, non posso dire alla mia azienda, "scarto di due anni di lavoro, e centinaia di migliaia di dollari spesi, basando il codice in questo quadro, a causa di errori di arrotondamento"

+10

+1 per il titolo "Raddoppia i miei soldi" – Marko

+5

Capisco la tua situazione. Ma: il denaro contante e la contabilità corretta tendono ad essere importanti per le aziende. Quindi fare cause legali per negligenza. Ti trovi in ​​una situazione difficile: da una parte, consiglia all'azienda di bruciare anni di sviluppo per risolvere quello che sembrava essere un problema. D'altra parte, invitare grandi problemi lungo la strada. Se fossi in te, lo direi alla mia direzione in quel modo, e fagli fare la chiamata. Hai un problema di responsabilità qui, ed è quello che il tuo capo è lì per. Documenta qualsiasi decisione presa. –

+2

Per quello che @ Michele Petrotta ha detto, è corretto al 100%. A seconda di ciò che stai facendo, potrebbero essere enormi problemi di responsabilità che arrivano da questa decisione. Inoltre, questo quadro - è un quadro commerciale? Potresti convincerli a cambiare in BigDecimal, o c'è una versione che lo fa?Altrimenti, avresti bisogno di dirigere la via del refactoring incrementale, che è una situazione orribile in cui ti trovi - provo per te. – aperkins

risposta

10

Se tollerabile, considera il tipo monetario come integrale. In altre parole, se lavori negli Stati Uniti, traccia i centesimi anziché i dollari, se i centesimi forniscono la granularità di cui hai bisogno. Le doppie possono rappresentare in modo accurato interi fino a a very large value (2^53) (nessun errore di arrotondamento fino a tale valore).

Ma in realtà, la cosa giusta da fare è bypassare completamente la struttura e utilizzare qualcosa di più ragionevole. Questo è un errore amatoriale da fare nel framework - chissà che altro c'è in agguato?

6

non ho visto Lei parla di refactoring. Penso che sia la tua migliore opzione qui. Invece di mettere insieme alcuni hack per far funzionare meglio le cose per ora, perché non risolverlo nel modo giusto?

Ecco alcune informazioni su double vs BigDecimal. Questo post suggerisce di utilizzare BigDecimal anche se è più lento.

+1

[Java efficace] (http://java.sun.com/docs/books/effective/) lo suggerisce anch'esso (seconda edizione, articolo 48). –

+2

Spero che suggerisca 'BigDecimal' ... la velocità non significa molto se i tuoi numeri e calcoli sono _incorrect_ a causa di' double'. – ColinD

+0

Dall'OP, sembra che * non possa * refactare come il framework (che presumo che non abbia il codice sorgente per) usa l'aritmetica FP. – gustafc

1

Beh, non c'è bisogno che molte opzioni in realtà:

È possibile refactoring del progetto per utilizzare ad esempio BigDecimal (o qualcosa che meglio si adatta alle sue esigenze) per rappresentare il denaro.

Prestare estrema attenzione al trabocco/underflow e alla perdita di precisione, il che significa aggiungere tonnellate di controlli e rielaborare in modo non necessario una parte ancora maggiore del sistema. Per non parlare di quanta ricerca sarebbe necessaria se si vuole farlo.

Tenere le cose come sono e spero che nessuno se ne accorga (è uno scherzo).

IMHO, la soluzione migliore sarebbe semplicemente refactoring questo fuori. Potrebbe essere un pesante refactoring, ma il male è già stato fatto e credo che dovrebbe essere la scelta migliore.

migliore, Vassil

P.S. Oh, puoi trattare i soldi come numeri interi (contando i centesimi), ma non sembra una buona idea se hai conversioni valutarie, calcolando interessi, ecc.

+0

Forse una perdita di precisione, ma "overflow/underflow" sei serio? –

+0

doppio overflow per valori superiori a 10^308 e underflow per valori inferiori a 10^-308. Quali valute hanno questo tipo di ordinamento? –

+0

Ok, overflow in altamente improbabile, ma non è possibile sapere quali operazioni sarebbero eseguite su questi numeri e underflow è una possibilità in alcuni scenari. – vstoyanov

2

Un sacco di persone suggeriranno di usare BigDecimal e se non sai come usare l'arrotondamento nel tuo progetto, questo è quello che dovresti fare.

Se si sa come utilizzare correttamente l'arrotondamento decimale, utilizzare doppio. I suoi numerosi ordini di grandezza più veloci, molto più chiari e più semplici e quindi meno IMHO incline agli errori. Se usi dollari e centesimi (o hai bisogno di due cifre decimali), puoi ottenere un risultato accurato per valori fino a 70 trilioni di dollari.

Fondamentalmente, non si ottengono errori di arrotondamento se si corregge utilizzando l'arrotondamento appropriato.

BTW: Il pensiero di errori di arrotondamento colpisce il terrore nel cuore di molti sviluppatori, ma non sono errori casuali ed è possibile gestirli abbastanza facilmente.

MODIFICA: considerare questo semplice esempio di errore di arrotondamento.

double a = 100000000.01; 
    double b = 100000000.09; 
    System.out.println(a+b); // prints 2.0000000010000002E8 

Esistono diverse possibili strategie di arrotondamento. È possibile arrotondare il risultato durante la stampa/la visualizzazione. per esempio.

System.out.printf("%.2f%n", a+b); // prints 200000000.10 

o rotondo il risultato matematicamente

double c = a + b; 
    double r= (double)((long)(c * 100 + 0.5))/100; 
    System.out.println(r); // prints 2.000000001E8 

Nel mio caso, ho arrotondare il risultato quando si invia dal server (scrivendo a una presa di corrente e un file), ma usare la mia routine per evitare qualsiasi creazione di oggetti.

Una funzione di round più generale è la seguente, ma se è possibile utilizzare printf o DecimalFormat, può essere più semplice.

private static long TENS[] = new long[19]; static { 
    TENS[0] = 1; 
    for (int i = 1; i < TENS.length; i++) TENS[i] = 10 * TENS[i - 1]; 
} 

public static double round(double v, int precision) { 
    assert precision >= 0 && precision < TENS.length; 
    double unscaled = v * TENS[precision]; 
    assert unscaled > Long.MIN_VALUE && unscaled < Long.MAX_VALUE; 
    long unscaledLong = (long) (unscaled + (v < 0 ? -0.5 : 0.5)); 
    return (double) unscaledLong/TENS[precision]; 
} 

nota: è possibile utilizzare BigDecimal per eseguire l'arrotondamento finale. esp se avete bisogno di un metodo rotondo specifico.

+2

Non ho effettuato il downvoting, ma senza fornire alcune indicazioni su come gestire questi errori di arrotondamento, la risposta non è utile. – Yishai

+0

Se hai a che fare con ingenti somme di denaro, questo non è pratico (pensa che milioni e miliardi di dollari vengano aggiunti/sottratti, ecc.). Inoltre, a seconda dell'hardware, gli errori di arrotondamento potrebbero essere diversi, il che fornirebbe potenziali punti critici futuri e potrebbe sicuramente causare comportamenti imprevedibili. Se è necessaria precisione, non si utilizzano strutture di dati stimati in virgola mobile (double/float) e invece si concentra invece sulla precisione. – aperkins

+0

Vale la pena ricordare che non ho neanche votato a downvote. – vstoyanov

0

Penso che questa situazione sia almeno minimamente recuperabile per il tuo codice. Ottieni il valore come doppio tramite il framework ORM. È quindi possibile convertirlo in BigDecimal utilizzando il metodo valueOf statico (vedere here per il motivo) prima di eseguire calcoli matematici su di esso e quindi riconvertirlo in double solo per memorizzarlo.

Poiché si stanno estendendo queste classi in ogni caso, è possibile aggiungere i getter per il valore doppio che li ottiene come BigDecimal quando è necessario.

Questo non può coprire il 100% dei casi (sarei particolarmente preoccupato di ciò che il driver ORM o JDBC sta facendo per convertire il doppio in un tipo numerico), ma è molto meglio che semplicemente fare i conti sui doppi grezzi.

Tuttavia, sono tutt'altro che convinto che questo approccio sia effettivamente più economico per l'azienda a lungo termine.

Problemi correlati