2012-07-06 11 views
6

Eventuali duplicati:
Why can't decimal numbers be represented exactly in binary?
problem with floating valuesPHP errori virgola mobile con base matematica

$var1 = 1; 

for ($i=0; $i<30; $i++) { 
    $var1 += 0.1; 
    $var2 = floor($var1); 
    $var3 = $var1-$var2; 
    if ($var3 == 0.5) { 
    $var1 = $var2+1; 
    } 
} 

L'intenzione di questo ciclo è di contare 1.0, 1.1, 1.2, 1.3, 1.4, quindi passare a 2.0, 2.1, 2.2, ecc.

Il problema che sto ottenendo è che l'istruzione if non è mai vera. Inoltre ogni decimo calcolo si risolve in una pazza risposta scientifica.

Come posso risolvere questo problema? per favore aiuto!

Modifica: Ho scritto la domanda in un momento un po 'frustrato ed era più di una, lo vedo ora.

La prima parte della domanda era "come posso fare questo lavoro superando questo quo virgola mobile" e "perché questo querk sta accadendo!"

Grazie per tutte le grandi risposte e sto votando la risposta come corretta che ha risposto facilmente alla domanda principale di "come fare questo lavoro".

Utilizzare 0,49 anziché 0,5 e> invece di ==. Crude e non il miglior codice al mondo, ma risolve la domanda originale. Grazie a tutti per le altre risposte che leggerò e seguirò per migliorare la mia codifica.

Ancora una volta, molte grazie.

+0

Forse postare quale "pazza risposta scientifica" è ??? – mathematician1975

+0

possibile duplicato di [problema con valori fluttuanti] (http://stackoverflow.com/questions/6503994/problem-with-floating-values), [Capire i numeri in virgola mobile in php] (http://stackoverflow.com/questions/10991713/understanding-floating-point-numbers-in-php), ecc. – fresskoma

+0

Un'ottima spiegazione dei problemi in virgola mobile: http://stackoverflow.com/questions/1089018/why-cant-decimal-numbers-be-represented-exactly-in-binary – mtrw

risposta

5

Questo dà desiderata:

$var1 = 1; 
for ($i=0; $i<30; $i++) { 
    $var1 += 0.1; 
    $var2 = (int)($var1); 
    $var3 = $var1-$var2; 
    if ($var3 >= 0.45) { 
     $var1 = $var2+1; 
    } 
    echo $var1.' '.$var2.' '.$var3.'<br/>'; 
} 
+2

Ho votato come risposta corretta perché, usando il mio codice, completamente inchioda una soluzione facile e veloce. Ho votato tutte le altre risposte perché si occupano di PERCHÉ. Lo rivisiterò più tardi ma purtroppo non ho molto tempo adesso. – Dorjan

0

L'istruzione if non può mai eseguire semplicemente a causa di aritmetica in virgola mobile in generale - il test di uguaglianza rigorosa con valori in virgola mobile porta sempre questo rischio. Perché non cambiare il condizionale per verificare che la variabile si trovi all'interno di un piccolo intervallo centrato a 0.5 per essere sicuro di prenderlo ???

0

Mantenerlo semplice, stupido e utilizzare l'intero aritmetico.

Per esempio si inizia a 1.1

Perché non:

risultati
for ($i=1; $i<4; $i++) { 
    for ($j=0; $j<5; $j++) { 
    $v = $i + ($j/10); 
    # ... 
    } 
} 
+2

-1 Per "Oh, la tua auto non lavoro? Bene, usa questa moto, invece! " – fresskoma

+0

@ x3ro, grazie per avermi fatto sapere perché -1 :-) Tuttavia, il problema qui è con confronti decimali in virgola mobile. Una soluzione perfettamente valida è di evitare del tutto il problema: utilizzare una forma algo che non faccia tali confronti. Suggerirei l'analogia: non riesco a far scendere la mia auto in quella stradina - hai una moto; perché non usarlo invece? _. – TerryE

+0

Farò +1 perché non c'è alcun motivo per -1. Esistono più soluzioni per un problema. Se una soluzione richiede un sacco di soluzioni (in questo caso, l'inesattezza di confrontare due float), perché non utilizzare un'altra soluzione che è più semplice e potrebbe essere più veloce. – invisal

3

Binary punto float sceglierà valore approssimativo di rappresentare base-10 del galleggiante punto che non può rappresentare. Pertanto, non è accurato confrontare il float con float (perché non è possibile determinarne il comportamento).

È necessario inventare il proprio punto di virgola mobile base 10. Non è difficile da inventare se sai quale preciso vuoi. Nel tuo caso, avrai bisogno solo di una cifra precisa.La formula per il nostro numero intero di base 10 sarebbe: valore/10 alla potenza della nostra cifra precisa che è 1, quindi la tua formula è = valore/10.

L'esecuzione di operazioni aritmetiche è facile come eseguire l'aritmetica normale. Basta convertire la rappresentazione normale (si riferisce alla rappresentazione che l'uso del computer) alla nostra rappresentazione inventata. Nel tuo caso, $var1 += 0.1 passerebbe a $var1 += 1.

L'output è facile come convertire la rappresentazione in rappresentazione normale. Nel tuo caso, echo $var1 . '<br>'; si trasformerebbe in echo $var1/10 . '<br>';

$var1 = 10; 

for ($i=0; $i<30; $i++) { 
    echo $var1/10 . '<br>'; 
    $var1 += 1; 
    if ($var1 % 10 == 5) { 
    $var1 = $var1+5; 
    } 
} 
1

una soluzione adeguata è quella di utilizzare il pacchetto bcmath:

$var1 = '1.0'; bcscale(1); 

for ($i = 0; $i < 30; $i++) { 
    $var2 = $var1; 
    for($j = 0; $j < 5; $j++) { 
     echo $var2 . "\n"; 
     $var2 = bcadd($var2, '0.1'); 
    } 
    $var1 = bcadd($var1, '1'); 
} 

Questo outputs the correct result.

2

Un sacco di buone informazioni sul perché questo accade e un numero di soluzioni. Uno degli obiettivi fondamentali del design di PHP è il suo design a caratteri larghi e il casting implicito. In questo spirito, penso che una delle soluzioni più semplici a questo problema sia l'uso del confronto tra stringhe. Nel tuo caso è un'ottima soluzione:

<?php 
$var1 = 1; 

for ($i=0; $i<30; $i++) { 
    $var1 += 0.1; 
    $var2 = floor($var1); 
    $var3 = $var1 - $var2; 
    echo "$var1 | $var2 | $var3 \n"; 
    if (number_format($var3, 1) == '0.5') { 
    echo "It's true\n"; 
    $var1 = $var2 + 1; 
    } 
}