Come gestire i valori monetari in PHP e MySql?


16

Ho ereditato un enorme mucchio di codice legacy scritto in PHP su un database MySQL. La cosa che ho notato è che l'applicazione utilizza doublesper l'archiviazione e la manipolazione dei dati.

Ora mi sono imbattuto in numerosi post che menzionano come doublenon sono adatti per le operazioni monetarie a causa degli errori di arrotondamento. Tuttavia, devo ancora trovare una soluzione completa su come i valori monetari dovrebbero essere gestiti nel codice PHP e archiviati in un database MySQL.

Esiste una buona pratica quando si tratta di gestire denaro in modo specifico in PHP?

Le cose che sto cercando sono:

  1. Come devono essere archiviati i dati nel database? tipo di colonna? taglia?
  2. Come devono essere gestiti i dati in aggiunta normale, sottrazione. moltiplicazione o divisione?
  3. Quando devo arrotondare i valori? Quanti arrotondamenti sono accettabili?
  4. C'è differenza tra gestire valori monetari elevati e valori bassi?

Nota: un codice di esempio MOLTO semplificato di come potrei incontrare i valori del denaro nella vita di tutti i giorni (per motivi di semplificazione sono stati ignorati vari problemi di sicurezza. Naturalmente nella vita reale non avrei mai usato il mio codice in questo modo):

$a= $_POST['price_in_dollars']; //-->(ex: 25.06) will be read as a string should it be cast to double?
$b= $_POST['discount_rate'];//-->(ex: 0.35) value will always be less than 1
$valueToBeStored= $a * $b; //--> any hint here is welcomed 

$valueFromDatabase= $row['price']; //--> price column in database could be double, decimal,...etc.

$priceToPrint=$valueFromDatabase * 0.25; //again cast needed or not?

Spero che tu usi questo codice di esempio come mezzo per mettere in evidenza più casi d'uso e non prenderlo alla lettera, ovviamente.

Domanda bonus Se devo usare un ORM come Doctrine o PROPEL, quanto sarà diverso usare il denaro nel mio codice.


1
Non conosco PHP ma conoscere la terminologia è inestimabile in questi scenari per google-fu, il termine che stai cercando è "precisione arbitraria" a cui google risponde con php.net/manual/en/book.bc.php
Jimmy Hoffa,

1
@Jimmy Hoffa: la precisione arbitraria non è generalmente ciò di cui hai bisogno, qualcosa che può rappresentare con precisione e lavorare con le frazioni decimali. Naturalmente, spesso si trovano entrambi combinati, ma ad esempio il decimaltipo in C # ha una precisione limitata ma è perfettamente adatto ai valori monetari.
Michael Borgwardt,

1
@MichaelBorgwardt giusto, mi dimentico dei tipi razionali perché così tante lingue non li hanno. Ottima scelta.
Jimmy Hoffa,

Avendo lavorato molto con le valute e il codice legacy, direi di conservarlo in numeri interi e usare centesimi anziché dollari. Attenzione, 32 numeri interi possono contenere una quantità sorprendentemente piccola.4) un numero intero a 32 bit può contenere solo un importo di 21 milioni se non firmato e usando centesimi.
Pieter B,

A parte questo, il tuo codice di esempio che mostra i prezzi e gli sconti POSTATI mi terrorizza. Si spera che sia così semplificato da non riflettere il reale utilizzo, ma a valore nominale, sembra che ti fidi del browser per dirti quale è il prezzo di un articolo postando un campo nascosto o qualcosa del genere.
Carson63000,

Risposte:


6

Può essere piuttosto complicato gestire i numeri con PHP / MySQL. Se usi il decimale (10,2) e il tuo numero è più lungo o ha una precisione maggiore, verrà troncato senza errori (a meno che tu non imposti la modalità corretta per il tuo server db).

Per gestire valori di grandi dimensioni o di alta precisione è possibile utilizzare una libreria come BCMath , che consente di eseguire operazioni di base su numeri di grandi dimensioni e mantenere la precisione richiesta.

Non sono sicuro di quali calcoli farai esattamente, ma devi anche tenere presente che (0,22 * 0,4576) + (0,78 * 0,4576) non sarà uguale a 0,4576 se non utilizzerai la precisione corretta durante il processo.

La dimensione massima di DECIMAL in MySQL è 65, quindi dovrebbe essere più che sufficiente per qualsiasi scopo. Se si utilizza il tipo di campo DECIMAL, verrà restituito come stringa indipendentemente dall'uso di un ORM o semplicemente PDO / mysql (i).

Come devono essere archiviati i dati nel database? tipo di colonna? taglia?

DECIMAL con la precisione di cui hai bisogno. Se stai utilizzando i tassi di cambio, avrai bisogno di almeno quattro cifre decimali

Come devono essere gestiti i dati in aggiunta normale, sottrazione. moltiplicazione o divisione?

Usa BCMath per essere al sicuro e perché usare float potrebbe non essere una buona idea

Quando devo arrotondare i valori? Quanti arrotondamenti sono accettabili?

Per i valori monetari sono accettabili due decimali normali, ma potresti averne bisogno di più se, ad esempio, stai utilizzando i tassi di cambio.

C'è differenza tra gestire valori monetari elevati e valori bassi?

Dipende da cosa intendi per grande. C'è sicuramente una differenza tra la gestione dei numeri con elevata precisione.


9

Una semplice soluzione consiste nel memorizzarli come numeri interi. 99,99 memorizzato come 9999. Se questo non funziona (e ci sono molti motivi per cui questa potrebbe essere una scelta sbagliata) puoi usare il tipo Decimale. http://dev.mysql.com/doc/refman/5.0/en/precision-math-decimal-changes.html sul lato mysql. Sul lato php ho trovato questo /programming/3244094/decimal-type-in-php che potrebbe essere quello che stai cercando.

Domanda bonus: difficile da dire. L'orm funzionerà in base ai tipi di dati scelti. Direi che potresti fare alcune cose con l'astrazione per aiutare, ma questo specifico problema non viene risolto semplicemente passando a un ORM.


1
FWIW, Drupal Commerce utilizza il trucco 9999 per memorizzare i suoi prezzi.
Florian Margaine,

1
"e ci sono molte ragioni per cui questa potrebbe essere una cattiva scelta" Vuoi condividere alcuni dei problemi con questa pratica?
Songo,


@MichaelBorgwardt Grazie per le informazioni, Songo scusa per il ritardo, l'uragano ci ha portati fuori :)
Ominus

3

Proverò a mettere la mia esperienza in questo:

Le cose che sto cercando sono:

Come devono essere archiviati i dati nel database? tipo di colonna? taglia?

Ho usato DECIMAL(10,2)per mysql senza problemi (8 completamenti e 2 decimali == 99.999.999,99 == quantità enorme), ma questo dipende dalla gamma di soldi che dovrai coprire. Enormi quantità dovrebbero essere prese con particolare attenzione (ad esempio valori di float max del sistema operativo). Nella parte decimale uso 2 valori per evitare troncamenti o arrotondamenti. Considerando i soldi ci sono alcuni casi in cui hai bisogno di più decimali (nel qual caso devi assicurarti che l'utente lavorerà con tutti loro, altrimenti sono dati inutili)

Come devono essere gestiti i dati in aggiunta normale, sottrazione. moltiplicazione o divisione?

Lavora con una valuta e una tabella di scambio (con date). In questo modo ti assicuri di avere sempre la quantità corretta salvata. Un extra: salva i valori completi e crea una vista con i risultati del calcolo. Questo ti aiuterà a fissare i valori al volo

Quando devo arrotondare i valori? Quanti arrotondamenti sono accettabili?

Ancora una volta, dipende dalla gamma di denaro del tuo sistema. Pensa sempre in termini KISS a meno che tu non debba cadere nel caos del cambio

C'è differenza tra gestire valori monetari elevati e valori bassi?

A seconda del sistema operativo e dei linguaggi di programmazione, è sempre necessario verificare i valori massimo e minimo


Grazie per la risposta, ma se devo gestire qualcosa del tipo $valueToBeStored= $a * $b;if $ae $bsono entrambi letti come decimali dal database, penso che verranno trasmessi doublein PHP, giusto? influenzerà i numeri?
Songo,

$ae $bsono presi da db? quindi, nel mio esempio non è necessario archiviare mai $valueToBeStoredperché avrai sempre l'origine $ae i $bdati. Quindi puoi lavorare programmaticamente il valore in una funzione o così o creare una vista mysql con il risultato della colonna. In questo modo, se è necessario modificare un valore, non è necessario preoccuparsi di modificare più posizioni (soggette a errori)
Alwin Kesler,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.