Hola, les saluda Luis y aquí les traigo este nuevo post.
Durante todos los años que he pasado codificando en C
, he evitado comparar números de punto flotante porque, a diferencia de la comparación de enteros, se sabe que producen resultados impredecibles, debido al método que los números de punto flotante se almacenan en la memoria.
Por ejemplo, si ejecuto el siguiente código, ¿cuál crees que será el resultado?
float n_0 = 1.0 / 2.0; float n_1 = 1.0 / 200.0; float n_2 = n_1 * 100.0; printf(n_0 == n_2 ? "You got lucky!" : "Not so accurate, are you?")
En casi todas las computadoras que hay bajo el sol, terminarás «teniendo suerte», y puedo apostar una fortuna en eso. Pero si estoy tan seguro del resultado, ¿por qué evito usar esta lógica para aplicaciones reales?
Para entender esto, echemos un vistazo a cómo se almacenan los números de punto flotante en la memoria. Cualquier número de coma flotante consta de tres partes que deben almacenarse: el signo, la parte entera y la parte fraccionaria.
Por ejemplo, el número 2.5
se puede almacenar como (signo = +, entero = 2, fraccionario = 0.5
). Otro método para almacenar números de punto flotante es almacenar el signo, un coeficiente entero de un solo dígito distinto de cero, una mantisa y un exponente, un método comúnmente conocido como notación científica.
En este método, 2.5
se puede representar como (signo = +, coeficiente = 2, mantisa = 0.5, exponente = 0
). Echemos un vistazo a algunos otros números representados en notación científica.
250.00, +2.5000000e+002 0.0025, +2.5000000e-002 -12.34, -1.2340000e+001 ...
Las computadoras almacenan números de punto flotante usando el segundo método y usan un número fijo de celdas para almacenar cada componente.
Por ejemplo, en los ejemplos anteriores, el signo toma la primera celda, el coeficiente toma la celda siguiente, siete celdas están reservadas para la mantisa y cuatro celdas se usan para el exponente, que nuevamente se divide en una celda para el signo y tres celdas para el valor.
Aritmética de punto flotante con dos números
Ahora, veamos cómo se realiza una aritmética de punto flotante con dos números, almacenados en este formato. Como ejemplo, intentaremos multiplicar dos números, digamos 1/2
y 12
.
Sabemos que 1/2
se representa como + 5.0000000e-001
, y 12
se representa como + 1.2000000e + 001
. Entonces la multiplicación se puede hacer como:
Como puede ver, debido a que la mantisa para ambos números tiene muchos ceros al final, el resultado de la multiplicación es inequívocamente 6
.
Eso es exactamente lo que estaba sucediendo en ese pequeño código que ejecutamos antes. Siempre que operemos con números de punto flotante que podrían representarse en nuestro formato sin ninguna pérdida de precisión, podemos estar seguros del resultado del cálculo.
Solo para ilustrar mi punto, veamos otro ejemplo, esta vez usando un número, que debe ser truncado para ajustarse a las celdas dadas y, por lo tanto, perder precisión. El siguiente ejemplo muestra la multiplicación entre 1/3
y 12
.
El dígito extra producido durante la multiplicación, que no cabe en las celdas proporcionadas, conduce al redondeo de los dígitos restantes en el coeficiente y la mantisa, y en este caso, dado que el dígito menos significativo 6
es mayor que 5
, el resultado se redondea a + 4.0000000e + 000
, que es exactamente igual a la respuesta real 4
.
Pero, ¿el redondeo siempre produce el resultado correcto? ¿Qué tal multiplicar 1/3
por 18
?
Esta es exactamente la razón por la que no podemos usar los resultados de los cálculos de punto flotante en las comparaciones. Si ejecuta el siguiente código, que es una pequeña modificación del que comenzamos, lo más probable es que lo vea usted mismo.
float n_0 = 1.0 / 7.0; float n_1 = 1.0 / 700.0; float n_2 = n_1 * 100.0;printf(n_0 == n_2 ? "You got lucky!" : "Not so accurate, are you?")
En conclusión
Aunque no mencioné bits
en ninguna parte anterior, y hablé principalmente en decimal, esto se puede extender fácilmente para que se ajuste a nuestras computadoras binarias.
La computadora almacena el signo como un solo bit
, el coeficiente no se almacena en absoluto (porque el único dígito distinto de cero en binario es 1
, por lo que no tiene sentido perder un poco para almacenar eso), y el exponente y la mantisa son almacenado usando un número fijo de bits dependiendo de la precisión deseada. Puede buscar el estándar IEEE-754
para obtener los detalles exactos.
Además, en este punto, si eres curioso acerca de lo que el resultado de multiplicar 1/3
y 15
sería, yo sugeriría que echar un vistazo a las reglas de redondeo de IEEE 754
.
Gracias por leer este post.
Añadir comentario