Domanda

Poiché il tipo di dati float in PHP non è accurato e un FLOAT in MySQL occupa più spazio di un INT (ed è inaccurato), memorizzo sempre i prezzi come INT, moltiplicando per 100 prima di archiviare per assicurarmi di avere esattamente 2 decimali luoghi di precisione. Tuttavia, credo che PHP si stia comportando male. Codice di esempio:

echo "<pre>";

$price = "1.15";
echo "Price = ";
var_dump($price);

$price_corrected = $price*100;
echo "Corrected price = ";
var_dump($price_corrected);


$price_int = intval(floor($price_corrected));
echo "Integer price = ";
var_dump($price_int);

echo "</pre>";

Output prodotto:

Price = string(4) "1.15"
Corrected price = float(115)
Integer price = int(114)

Sono stato sorpreso. Quando il risultato finale era inferiore del previsto di 1, mi aspettavo che l'output del mio test fosse più simile a:

Price = string(4) "1.15"
Corrected price = float(114.999999999)
Integer price = int(114)

che dimostrerebbe l'imprecisione del tipo float. Ma perché floor (115) restituisce 114 ??

È stato utile?

Soluzione

Prova questa soluzione rapida:

$price_int = intval(floor($price_corrected + 0.5));

Il problema che stai riscontrando non è colpa di PHP, tutti i linguaggi di programmazione che utilizzano numeri reali con aritmetica in virgola mobile hanno problemi simili.

La regola generale per i calcoli monetari è di non usare mai i float (né nel database né nel tuo script). Puoi evitare tutti i tipi di problemi archiviando sempre i centesimi anziché i dollari. I centesimi sono numeri interi e puoi aggiungerli liberamente insieme e moltiplicarli per altri numeri interi. Ogni volta che visualizzi il numero, assicurati di inserire un punto davanti alle ultime due cifre.

Il motivo per cui ottieni 114 anziché 115 è che floor viene arrotondato per difetto, verso il numero intero più vicino, quindi floor (114.999999999) diventa 114. La domanda più interessante è perché 1.15 * 100 è 114.999999999 invece di 115. Il motivo è che 1.15 non è esattamente 115/100, ma è un po 'meno, quindi se si moltiplica per 100, si ottiene un numero un po' più piccolo di 115.

Ecco una spiegazione più dettagliata di cosa fa echo 1.15 * 100; :

  • Analizza 1.15 in un numero binario in virgola mobile. Ciò comporta un arrotondamento, capita di arrotondare un po 'per ottenere il numero in virgola mobile binario più vicino a 1,15. Il motivo per cui non è possibile ottenere un numero esatto (senza errore di arrotondamento) è che 1.15 ha un numero infinito di numeri nella base 2.
  • Analizza 100 in un numero binario in virgola mobile. Ciò comporta l'arrotondamento, ma poiché 100 è un numero intero piccolo, l'errore di arrotondamento è zero.
  • Calcola il prodotto dei due numeri precedenti. Ciò comporta anche un piccolo arrotondamento, per trovare il numero in virgola mobile binario più vicino. L'errore di arrotondamento sembra essere zero in questa operazione.
  • Converte il numero binario in virgola mobile in un numero decimale di base 10 con un punto e stampa questa rappresentazione. Ciò comporta anche un piccolo arrotondamento.

Il motivo per cui PHP stampa il sorprendente prezzo corretto = float (115) (anziché 114.999 ...) è che var_dump non stampa il numero esatto ( !), ma stampa il numero arrotondato alle cifre n - 2 (o n - 1 ), dove n cifre è la precisione del calcolo. Puoi verificarlo facilmente:

echo 1.15 * 100;  # this prints 115
printf("%.30f", 1.15 * 100);  # you 114.999....
echo 1.15 * 100 == 115.0 ? "same" : "different";  # this prints `different'
echo 1.15 * 100 < 115.0 ? "less" : "not-less";    # this prints `less'

Se stai stampando float, ricorda: non sempre vedi tutte le cifre quando stampi il float .

Vedi anche il grande avvertimento vicino all'inizio dei float PHP .

Altri suggerimenti

Le altre risposte hanno coperto la causa e credo che una buona soluzione al problema.

Per mirare a risolvere il problema da una diversa angolazione:

Per memorizzare i valori dei prezzi in MySQL, dovresti probabilmente guardare Tipo DECIMAL , che consente di memorizzare valori esatti con cifre decimali.

PHP sta eseguendo l'arrotondamento in base a cifre significative. Nasconde l'imprecisione (sulla linea 2). Ovviamente, quando arriva il pavimento, non sa niente di meglio e lo fa scivolare fino in fondo.

Forse è un'altra possibile soluzione per questo "problema":

intval(number_format($problematic_float, 0, '', ''));

Come affermato, questo non è un problema con PHP in sé, è più una questione di gestione delle frazioni che non possono essere espresse come valori a virgola mobile finiti, portando quindi alla perdita di carattere durante l'arrotondamento.

La soluzione è garantire che quando si lavora su valori in virgola mobile e è necessario mantenere la precisione - utilizzare le funzioni gmp o BC matematiche - bcpow, bcmul et al. e il problema sarà risolto facilmente.

Es. invece di $ price_corrected = $ price * 100;

usa $ price_corrected = bcmul ($ price, 100);

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top