Il modo migliore per memorizzare i valori di valuta in C ++

So che un float non è appropriato per memorizzare i valori di valuta a causa di errori di arrotondamento. Esiste un modo standard per rappresentare i soldi in C ++?

Ho cercato nella libreria boost e non ho trovato nulla a riguardo. In Java, sembra che BigInteger sia il modo, ma non ho trovato un equivalente in C ++. Potrei scrivere la mia class di denaro, ma preferisco non farlo se c’è qualcosa di testato.

Non archiviarlo solo come centesimi, dal momento che accumulerai errori moltiplicando per tasse e interessi piuttosto rapidamente. Per lo meno, mantieni due cifre in più significative: $ 12,45 verrebbero archiviate come 124,500. Se lo tieni in un intero con segno a 32 bit, avrai 200.000 $ con cui lavorare (positivo o negativo). Se hai bisogno di numeri più grandi o più precisione, un intero con segno a 64 bit probabilmente ti darà tutto lo spazio di cui avrai bisogno per un lungo periodo di tempo.

Potrebbe essere di qualche aiuto avvolgere questo valore in una class, per darti un posto per creare questi valori, fare aritmetica su di essi e formattarli per la visualizzazione. Questo ti darebbe anche un posto centrale per portare in giro quale valuta è stata conservata (USD, CAD, EURO, ecc.).

Avendo affrontato questo problema con i sistemi finanziari attuali, posso dirvi che probabilmente si desidera utilizzare un numero con almeno 6 posizioni decimali di precisione (supponendo che sia in USD). Si spera che, visto che stai parlando di valori valutari, non andrai fuori di testa qui. Ci sono proposte per l’aggiunta di tipi decimali a C ++, ma non conosco nessuno che sia ancora in circolazione.

Il miglior tipo nativo di C ++ da usare qui sarebbe il doppio lungo.

Il problema con altri approcci che usano semplicemente un int è che devi immagazzinare di più dei tuoi centesimi. Spesso le transazioni finanziarie sono moltiplicate per valori non interi e questo ti porterà nei guai dato che $ 100,25 tradotto a 10025 * 0,000123523 (es. APR) causerà problemi. Finirai per finire in terra in virgola mobile e le conversioni ti costeranno molto.

Ora il problema non si verifica nella maggior parte delle situazioni semplici. Ti darò un esempio preciso:

Dato diverse migliaia di valori di valuta, se si moltiplica ciascuno di una percentuale e quindi si aggiungono, si otterrà un numero diverso rispetto a se si fosse moltiplicato il totale per quella percentuale se non si mantengono abbastanza cifre decimali. Ora questo potrebbe funzionare in alcune situazioni, ma spesso ti farai perdere parecchi centesimi. Nella mia esperienza generale, assicurandoti di mantenere una precisione fino a 6 cifre decimali (assicurandoti che la restante precisione sia disponibile per la parte del numero intero).

Capisci anche che non importa che tipo lo memorizzi se fai matematica in modo meno preciso. Se la tua matematica viene eseguita in un’unica terra di precisione, non importa se la stai memorizzando in doppia precisione. La tua precisione sarà corretta per il calcolo meno preciso.


Detto questo, se non fai la matematica oltre alla semplice addizione o sottrazione e poi memorizza il numero, allora starai bene, ma non appena si presenterà qualcosa di più complesso di quello, sarai nei guai.

Esaminare la libreria matematica virgola mobile virgola mobile Intelr relativamente recente. È specifico per applicazioni finanziarie e implementa alcuni dei nuovi standard per l’aritmetica in virgola mobile in binario (IEEE 754r) .

Il più grande problema è l’arrotondamento stesso!

19% di 42,50 € = 8.075 €. A causa delle regole tedesche per arrotondare questo è 8,08 €. Il problema è che (almeno sulla mia macchina) 8.075 non può essere rappresentato come doppio. Anche se cambio la variabile nel debugger con questo valore, finisco con l’8,0749999 ….

Ed è qui che la mia funzione di arrotondamento (e qualsiasi altra sulla logica in virgola mobile a cui riesco a pensare) non riesce, poiché produce 8,07 €. La cifra significativa è 4 e quindi il valore è arrotondato per difetto. E questo è assolutamente sbagliato e non puoi fare nulla a meno che tu non eviti di utilizzare i valori in virgola mobile laddove ansible.

Funziona benissimo se rappresenti 42,50 € come numero intero 42500000.

42500000 * 19/100 = 8075000. Ora è ansible applicare la regola di arrotondamento sopra 8080000. Questo può essere facilmente trasformato in un valore di valuta per motivi di visualizzazione. 8,08 €.

Ma lo concluderei sempre in una class.

Ti suggerirei di mantenere una variabile per il numero di centesimi anziché di dollari. Questo dovrebbe rimuovere gli errori di arrotondamento. Visualizzarlo nel formato standard dollari / centesimi dovrebbe essere una preoccupazione.

Qualunque sia il tipo che decidi, ti consiglio di inserirlo in un “typedef” in modo da poterlo cambiare in un altro momento.

Conosci la tua gamma di dati.

Un valore float è valido solo per 6-7 cifre di precisione, quindi ciò significa un massimo di circa + -9999,99 senza arrotondamento. È inutile per la maggior parte delle applicazioni finanziarie.

Un double vale per 13 cifre, quindi: + -99,999,999,999,99, State ancora attenti quando si usano numeri grandi. Riconoscere la sottrazione di due risultati simili rimuove la maggior parte della precisione (vedere un libro sull’analisi numerica per potenziali problemi).

Il numero intero a 32 bit va bene a + -2 miliardi (il ridimensionamento a pochi centesimi scenderà di 2 cifre decimali)

Il numero intero a 64 bit gestirà tutti i soldi, ma ancora una volta, fai attenzione durante la conversione e moltiplicando per varie velocità nella tua app che potrebbero essere float / doppi.

La chiave è capire il tuo dominio del problema. Quali requisiti legali hai per la precisione? Come mostrerai i valori? Quanto spesso avverrà la conversione? Hai bisogno di internazionalizzazione? Assicurati di poter rispondere a queste domande prima di prendere una decisione.

Puoi provare il tipo di dati decimali:

https://github.com/vpiotr/decimal_for_cpp

Progettato per memorizzare valori orientati al denaro (saldo monetario, tasso di cambio, tasso di interesse), precisione definita dall’utente. Fino a 19 cifre.

È una soluzione di sola intestazione per C ++.

Dipende dalle tue esigenze aziendali in merito all’arrotondamento. Il modo più sicuro consiste nel memorizzare un numero intero con la precisione richiesta e sapere quando / come applicare l’arrotondamento.

Dici di aver guardato nella libreria di boost e non hai trovato niente lì. Ma ci sono multiprecision / cpp_dec_float che dice:

La radice di questo tipo è 10. Come risultato può comportarsi in modo leggermente diverso dai tipi di base-2.

Quindi, se stai già utilizzando Boost, questo dovrebbe essere buono per i valori e le operazioni di valuta, come il suo numero di base 10 e la precisione di 50 o 100 cifre (molto).

Vedere:

#include  #include  #include  int main() { float bogus = 1.0 / 3.0; boost::multiprecision::cpp_dec_float_50 correct = 1.0 / 3.0; std::cout << std::setprecision(16) << std::fixed << "float: " << bogus << std::endl << "cpp_dec_float: " << correct << std::endl; return 0; } 

Produzione:

float: 0.3333333432674408

cpp_dec_float: 0.3333333333333333

* Non sto dicendo che float (base 2) è cattivo e decimale (base 10) è buono. Si comportano in modo diverso ...

** So che questo è un vecchio post e boost :: la multiprecisione è stata introdotta nel 2013, quindi volevo rimarcarlo qui.

Numeri interi, sempre – archivialo come centesimi (o qualunque sia la valuta più bassa dove si sta programmando.) Il problema è che, qualunque cosa facciate con virgola mobile, un giorno troverete una situazione in cui il calcolo sarà diverso se lo fate in virgola mobile Arrotondare all’ultimo minuto non è la risposta dato che i calcoli della valuta reale sono arrotondati mentre vanno.

Non è ansible evitare il problema modificando l’ordine delle operazioni: questo fallisce quando si ha una percentuale che ti lascia senza una corretta rappresentazione binaria. I ragionieri faranno impazzire se sei fuori da un solo centesimo.

Raccomanderei di usare un int lungo per memorizzare la valuta nella denominazione più piccola (ad esempio, il denaro americano sarebbe centesimi), se si utilizza una valuta decimale.

Molto importante: assicurati di nominare tutti i tuoi valori di valuta in base a ciò che contengono effettivamente. (Esempio: account_balance_cents) Questo eviterà molti problemi lungo la linea.

(Un altro esempio in cui ciò si presenta sono le percentuali. Non nominare mai un valore “XXX_percent” quando in realtà contiene un rapporto non moltiplicato per cento).

La libreria GMP ha implementazioni “bignum” che è ansible utilizzare per calcoli di numeri interi arbitrari necessari per gestire il denaro. Vedi la documentazione per mpz_class (attenzione: questo è orribilmente incompleto, ma viene fornita una gamma completa di operatori aritmetici) .

Un’opzione è di memorizzare $ 10,01 come 1001 e fare tutti i calcoli in centesimi, dividendo per 100D quando si visualizzano i valori.

In alternativa, usa i float e arrotondati solo all’ultimo momento.

Spesso i problemi possono essere mitigati modificando l’ordine delle operazioni.

Invece del valore * .10 per uno sconto del 10%, usa (valore * 10) / 100, che ti aiuterà in modo significativo. (ricorda .1 è un binario ripetuto)

Il nostro istituto finanziario usa “doppio”. Dato che siamo un negozio a “reddito fisso”, abbiamo molti algoritmi complicati che usano il doppio in ogni caso. Il trucco è assicurarsi che la presentazione dell’utente finale non superi la precisione del doppio. Ad esempio, quando abbiamo un elenco di scambi con un totale di trilioni di dollari, dobbiamo essere sicuri che non stampiamo rifiuti a causa di problemi di arrotondamento.

vai avanti e scrivi i tuoi soldi ( http://junit.sourceforge.net/doc/testinfected/testing.htm ) o currency () (a seconda di cosa ti serve). e testarlo.

La soluzione è semplice, memorizza in base alla precisione richiesta, come un numero intero spostato. Ma quando si legge in convert a double float, i calcoli subiscono meno errori di arrotondamento. Quindi, quando la memorizzazione nel database si moltiplica a seconda della precisione di un intero, ma prima di troncare come un intero aggiungere +/- 1/10 per compensare gli errori di troncamento, o +/- 51/100 per arrotondare. Vai tranquillo.

Vorrei usare firmato a lungo per 32-bit e firmato a lungo lungo per 64-bit. Questo ti darà la massima capacità di archiviazione per la quantità sottostante stessa. Quindi svilupperei due manipolatori personalizzati. Uno che converte quella quantità basata sui tassi di cambio e quella che formatta quella quantità nella vostra valuta di scelta. Puoi sviluppare più manipolatori per varie operazioni / regole finanziarie.

Memorizza il dollaro e il centesimo come due interi separati.