Comprender los números de coma flotante en php

Sé que estas preguntas pueden hacerse muchas veces, pero a partir de mis lecturas y pruebas, me confundieron un poco y muchas de las lecturas que he hecho me han confundido aún más, ya que son bastante complejas.

Algunas personas parecen tener problemas con las comparaciones simples, sin embargo, yo mismo no he tenido problemas.

Por ejemplo...

$num1 = 27.64;
$num2 = 27.64;

if ($num1 == $num2) {
    echo 'Good!';
} else {
    echo 'Bad!';
}

// Echo's "Good!"

Y ...

$num1 = 27.60;
$num2 = 27.6;

if ($num1 == $num2) {
    echo 'Good!';
} else {
    echo 'Bad!';
}

// Echo's Good

Y ...

$num1 = 27.60;
$num2 = 57.60;

if ($num1 <= $num2) {
    echo 'Good!';
} else {
    echo 'Bad!';
}

// Echo's Good

Y ...

$num1 = 25.00;
$num2 = 12.50 + 12.5;

if ($num1 == $num2) {
    echo 'Good!';
} else {
    echo 'Bad!';
}

// Echo's Good

Entonces veo páginas como http://patchlog.com/php/comparing-float-values-in-php/ que parecen tener problemas simples y no lo entiendo.

Solo quiero entender cómo está teniendo problemas con su código simple, pero no con el mío.

preguntado el 12 de junio de 12 a las 07:06

6 Respuestas

ejemplo 1

Esos valores serán los mismos: asigna el mismo literal decimal a cada variable. Compara eso con este código:

$num1 = 27.64;
$num2 = 10.0 + 2.88 + 2.88 + 2.88 + 9.0; //In decimal arithmetic adds to 27.64

if ($num1 == $num2) {
    echo 'Good!';
} else {
    echo 'Bad!';
}

// Echo's "Bad!"

$num2 parece que debería ser 27.64, pero realmente suma algo como 27.639999999999997015720509807579219341278076171875 (eso es lo que obtengo cuando hago ese cálculo en Visual C++ en mi máquina). $num1 = 27.6400000000000005684341886080801486968994140625 (en mi máquina), por lo que difieren.

ejemplo 2

El 0 final no hace ninguna diferencia.

ejemplo 3

Los números no están dentro de la "tolerancia" de coma flotante, por lo que, por supuesto, serán diferentes.

ejemplo 4

12.5 es exactamente representable en coma flotante, por lo que 12.5 + 12.5 también lo es (0.5 es 2^-1).

Respondido el 12 de junio de 12 a las 14:06

Buena explicación. ¿Tiene algún consejo sobre cómo trabajar con números de coma flotante para evitar problemas como este? - Bordo

@Brett: Hay un montón de información sobre este tema en stackoverflow y en otros lugares. Un lugar para buscar los errores de redondeo que ocurren durante la conversión de decimal a punto flotante es mi blog, explorandobinary.com (en particular, comience con explorandobinary.com/topics/… ). - rick regan

Aquí hay un ejemplo claro:

$a = 0;
for ($i = 0; $i < 100000; $i++) {
    $a += 0.00001;
}
print("$a\n");

Esperarías obtener 1 impreso, pero en realidad la salida es 0.99999999999808.

(el resultado es una arquitectura x86_64)

Respondido el 12 de junio de 12 a las 07:06

Cuanto más grande (o más pequeño) el número de coma flotante, menos preciso es. La precisión exacta variará según la arquitectura del procesador.

Intenta hacer todas tus pruebas en 1E30 o 1E-30...

Respondido el 12 de junio de 12 a las 07:06

Los dos primeros tienen el valor proporcionado por el compilador, que resuelve ambos números en el mismo patrón de bits.

No voy a tocar el tercero ya que debería ser obvio por qué funciona.

Para el cuarto, los valores utilizados tienen patrones de bits totalmente precisos y bien definidos. Intente usar números un poco más fuera de lo común.

Respondido el 12 de junio de 12 a las 07:06

Puedes probar

 $a = '12.30';

como cadena para obtener una coincidencia exacta; de lo contrario, floatbox elimina por defecto el final '0'.

Respondido el 12 de junio de 12 a las 08:06

Esto se desmoronará tan pronto como hagas algún cálculo con él. - Ignacio Vázquez-Abrams

Los errores de coma flotante ocurren solo cuando hay operaciones cuyos resultados matemáticos no se pueden representar exactamente en coma flotante. Los errores están definidos con precisión; no son aleatorios ni arbitrarios, por lo que se producen resultados idénticos cuando se realizan operaciones idénticas.

En su primer ejemplo, asigna "27.64" a $num1 y a $num2. Aquí se está realizando una operación: el analizador debe interpretar la cadena de caracteres "27.64" y producir un resultado de punto flotante. Probablemente, el analizador produce el número de coma flotante más cercano a 27.64. (Como número hexadecimal de coma flotante, ese número es 0x1.ba3d70a3d70a4p+4. La parte anterior a la "p" es un número hexadecimal, con una parte fraccionaria. "p4" significa multiplicar por 24. Como número decimal, es 27.6400000000000005684341886080801486968994140625. Y produce el mismo número en ambos casos, por lo que la comparación de $num1 con $num2 indica que son iguales entre sí, aunque ninguno es igual a 27.64 (porque 27.64 no se puede representar exactamente en punto flotante).

En su segundo ejemplo, el número de punto flotante más cercano al valor del número "27.60" es el mismo que el número de punto flotante más cercano al valor del número "27.6", ya que los dos números representan el mismo valor Entonces, nuevamente, obtienes resultados idénticos.

En su tercer ejemplo, los valores de los dos números están muy separados, por lo que obtiene diferentes números de punto flotante y la comparación indica que no son iguales.

En su cuarto ejemplo, todos los valores se pueden representar exactamente en punto flotante, por lo que no hay error. 25, 12.50 y 12.5 son múltiplos pequeños de una potencia de dos (incluye potencias con un exponente negativo, como 5 = 2-1, dentro del rango del tipo de punto flotante, por lo que son representables. Además, la suma de 12.50 y 12.5 se puede representar exactamente, por lo que no hay error de redondeo al sumarlos. Por lo tanto, todos los resultados son exactos y la comparación indica que la suma es igual a 25.

Los problemas surgen cuando las personas esperan resultados idénticos de dos cálculos diferentes que tendrían el mismo resultado matemático. Un ejemplo clásico es comparar ".3" con ".1+.2". Convertir el número ".3" a coma flotante produce el valor representable más cercano, que es 0x1.3333333333333p-2 (0.299999999999999988897769753748434595763683319091796875), ligeramente por debajo de .3. La conversión de ".1" a coma flotante produce el valor representable más cercano, que es 0x1.999999999999ap-4 (0.1000000000000000055511151231257827021181583404541015625), ligeramente superior a .1. La conversión de ".2" a coma flotante produce el valor representable más cercano, que es 0x1.999999999999ap-3 (0.200000000000000011102230246251565404236316680908203125), ligeramente superior a .2. Al sumar los dos últimos valores, se obtiene el valor representable más cercano a su suma, que es 0x1.3333333333334p-2 (0.3000000000000000444089209850062616169452667236328125). Como puede ver, esa suma es diferente del valor obtenido al convertir ".3" a punto flotante, por lo que compararlos indica que son desiguales.

Respondido el 12 de junio de 12 a las 16:06

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.