2012-07-06 10 views
6

Duplicar posibles:
Why can't decimal numbers be represented exactly in binary?
problem with floating valuesPHP errores de punto flotante con las matemáticas básicas

$var1 = 1; 

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

La intención de este bucle es contar 1.0, 1.1, 1.2, 1.3, 1.4, y luego saltar a 2.0, 2.1, 2.2, etc.

El problema que estoy recibiendo es que la declaración if nunca es verdadera. Además, cada décimo cálculo se resuelve en una respuesta científica insana.

¿Cómo puedo solucionar esto? ¡por favor ayuda!

Editar: Escribí la pregunta en un poco de prisa frustrada y fue más de uno, lo veo ahora.

La primera parte de la pregunta realmente fue "¿cómo puedo hacer que este trabajo pase por alto este querk de punto flotante" y "por qué está sucediendo este querk"?

Gracias por todas las buenas respuestas y estoy votando la respuesta como correcta que responde fácilmente a la pregunta central de "cómo hacer que esto funcione".

Usando 0.49 en lugar de 0.5 y> en lugar de == lo hace. Crudo y no es el mejor código del mundo pero resuelve la pregunta original. Gracias a todos por las otras respuestas que voy a leer y seguir para mejorar mi codificación.

Una vez más, muchas gracias.

+0

Posiblemente publicar lo que la "respuesta científica insane" es ??? – mathematician1975

+0

posible duplicación de [problema con valores flotantes] (http://stackoverflow.com/questions/6503994/problem-with-floating-values), [Comprensión de los números de coma flotante en php] (http://stackoverflow.com/questions/10991713/understanding-floating-point-numbers-in-php), etc.pp. – fresskoma

+0

Una gran explicación de los problemas de punto flotante: http://stackoverflow.com/questions/1089018/why-cant-decimal-numbers-be-represented-excaly-in-binary – mtrw

Respuesta

5

Esto da deseada:

$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

He votado que esta es la respuesta correcta ya que, utilizando mi código, crea una solución rápida y fácil. He votado todas las demás respuestas porque se refieren a POR QUÉ. Voy a volver a visitar esto más tarde, pero lamentablemente no tengo mucho tiempo en este momento. – Dorjan

0

La sentencia if nunca se puede ejecutar simplemente debido a la aritmética de punto flotante en general - probando la igualdad estricta con los valores de punto flotante siempre lleva este riesgo. ¿Por qué no cambiar el condicional para verificar que la variable se encuentre dentro de un pequeño intervalo centrado en 0.5 para asegurarse de atraparlo?

0

Manténgalo simple, estúpido y use aritmética de números enteros.

Por ejemplo, usted empezar a 1,1

Por qué no:

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

-1 Para "Oh, su auto no ¿Funciona? Bueno, utiliza esta motocicleta en su lugar! " – fresskoma

+0

@ x3ro, gracias por dejarme saber por qué -1 :-) Sin embargo, el problema aquí es con las comparaciones decimales en el punto flotante binario. Una solución perfectamente válida es evitar el problema por completo: use un formulario de algo que no haga tales comparaciones. Sugeriría la analogía: no puedo llevar mi automóvil por ese carril angosto, tienes una motocicleta; ¿Por qué no usarlo en su lugar? _. – TerryE

+0

Haré +1 porque no hay ninguna razón para -1. Hay múltiples soluciones para un problema. Si una solución necesita una gran cantidad de solución (en este caso, la inexactitud de comparar dos flotadores), ¿por qué no utilizar otra solución que es más simple y podría ser más rápida? – invisal

3

binario punto flotante elegirá valor aproximado para representar base 10 del flotador punto que no puede representar. Por lo tanto, no es exacto comparar float para flotar (porque no se puede determinar su comportamiento).

Necesita inventar su propio punto de flotación base-10. No es difícil de inventar si sabes qué precisión quieres. En su caso, solo necesitará un dígito preciso.La fórmula para nuestro entero de base 10 sería: valor/10 a la potencia de nuestro dígito preciso que es 1, por lo tanto, su fórmula es = valor/10.

Realizar operaciones aritméticas es tan fácil como realizar operaciones aritméticas normales. Simplemente convierta la representación normal (se refiere a la representación que usa la computadora) a nuestra representación inventada. En su caso, $var1 += 0.1 pasaría a $var1 += 1.

La salida sería tan fácil como convertir su representación en representación normal. En su caso, se convertiría en echo $var1 . '<br>';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 solución adecuada es utilizar el paquete de 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'); 
} 

Este outputs the correct result.

2

Mucha buena información sobre por qué sucede esto y una serie de soluciones. Uno de los objetivos fundamentales del diseño de PHP es su diseño de tipo suelto y su fundición implícita. Con ese espíritu, creo que una de las soluciones más simples para este problema es usar la comparación de cadenas. En su caso, es una gran solución:

<?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; 
    } 
} 
Cuestiones relacionadas