¿Por qué algunos números pierden precisión cuando se almacenan como números de coma flotante?
Por ejemplo, el número decimal 9.2
se puede expresar exactamente como una relación de dos enteros decimales ( 92/10
), los cuales se pueden expresar exactamente en binario ( 0b1011100/0b1010
). Sin embargo, la misma proporción almacenada como un número de coma flotante nunca es exactamente igual a 9.2
:
32-bit "single precision" float: 9.19999980926513671875
64-bit "double precision" float: 9.199999999999999289457264239899814128875732421875
¿Cómo puede un número aparentemente simple ser "demasiado grande" para expresarlo en 64 bits de memoria?
floating-point
language-agnostic
precision
mhlester
fuente
fuente
Respuestas:
En la mayoría de los lenguajes de programación, los números de coma flotante se representan de manera muy similar a la notación científica : con un exponente y una mantisa (también llamada significado). Un número muy simple, por ejemplo
9.2
, es en realidad esta fracción:Donde está el exponente
-49
y está la mantisa5179139571476070
. La razón por la que es imposible representar algunos números decimales de esta manera es que tanto el exponente como la mantisa deben ser enteros. En otras palabras, todos los flotadores deben ser un número entero multiplicado por una potencia entera de 2 .9.2
puede ser simple92/10
, pero 10 no puede expresarse como 2 n si n está limitado a valores enteros.Ver los datos
Primero, algunas funciones para ver los componentes que hacen un 32 y 64 bits
float
. Pase por alto estos si solo le importa la salida (ejemplo en Python):Hay una gran cantidad de complejidad detrás de esa función, y sería bastante tangente explicarlo, pero si le interesa, el recurso importante para nuestros propósitos es el módulo de estructura .
Python
float
es un número de doble precisión de 64 bits. En otros lenguajes como C, C ++, Java y C #, la precisión doble tiene un tipo separadodouble
, que a menudo se implementa como 64 bits.Cuando llamamos a esa función con nuestro ejemplo
9.2
, esto es lo que obtenemos:Interpretando los datos
Verá que he dividido el valor de retorno en tres componentes. Estos componentes son:
Firmar
El signo se almacena en el primer componente como un solo bit. Es fácil de explicar:
0
significa que el flotador es un número positivo;1
significa que es negativo Porque9.2
es positivo, nuestro valor de signo es0
.Exponente
El exponente se almacena en el componente medio como 11 bits. En nuestro caso
0b10000000010
,. En decimal, eso representa el valor1026
. Una peculiaridad de este componente es que debe restar un número igual a 2 (# de bits) - 1 - 1 para obtener el verdadero exponente; en nuestro caso, eso significa restar0b1111111111
(número decimal1023
) para obtener el verdadero exponente0b00000000011
(número decimal 3).Mantissa
La mantisa se almacena en el tercer componente como 52 bits. Sin embargo, también hay una peculiaridad en este componente. Para comprender esta peculiaridad, considere un número en notación científica, como este:
La mantisa sería la
6.0221413
. Recuerde que la mantisa en notación científica siempre comienza con un solo dígito distinto de cero. Lo mismo es cierto para el binario, excepto que el binario solo tiene dos dígitos:0
y1
. ¡Entonces la mantisa binaria siempre comienza con1
! Cuando se almacena un flotador,1
se omite el frente de la mantisa binaria para ahorrar espacio; tenemos que volver a colocarlo al frente de nuestro tercer elemento para obtener la verdadera mantisa:Esto implica algo más que una simple adición, porque los bits almacenados en nuestro tercer componente en realidad representan la parte fraccional de la mantisa, a la derecha del punto de raíz .
Cuando tratamos con números decimales, "movemos el punto decimal" multiplicando o dividiendo por potencias de 10. En binario, podemos hacer lo mismo multiplicando o dividiendo por potencias de 2. Dado que nuestro tercer elemento tiene 52 bits, dividimos por 2 52 para moverlo 52 lugares a la derecha:
En notación decimal, eso es lo mismo que dividir
675539944105574
entre4503599627370496
para obtener0.1499999999999999
. (Este es un ejemplo de una relación que se puede expresar exactamente en binario, pero solo aproximadamente en decimal; para más detalles, consulte: 675539944105574/4503599627370496 ).Ahora que hemos transformado el tercer componente en un número fraccionario, la suma
1
da la verdadera mantisa.Recapitulando los componentes
0
para positivo,1
para negativo1
para obtener la verdadera mantisaCalcular el número
Al unir las tres partes, se nos da este número binario:
Que luego podemos convertir de binario a decimal:
Y multiplique para revelar la representación final del número con el que comenzamos (
9.2
) después de ser almacenado como un valor de coma flotante:Representando como una fracción
9.2
Ahora que hemos construido el número, es posible reconstruirlo en una fracción simple:
Cambia la mantisa a un número entero:
Convierte a decimal:
Resta el exponente:
Convierta el exponente negativo en división:
Multiplicar exponente:
Que es igual a:
9.5
Ya puedes ver que la mantisa tiene solo 4 dígitos seguidos de muchos ceros. Pero vamos a través de los pasos.
Montar la notación científica binaria:
Desplaza el punto decimal:
Resta el exponente:
Binario a decimal:
Exponente negativo a la división:
Multiplicar exponente:
Igual a:
Otras lecturas
fuente
Esta no es una respuesta completa ( mhlester ya cubrió mucho terreno bueno que no duplicaré), pero me gustaría enfatizar cuánto depende la representación de un número de la base en la que está trabajando.
Considere la fracción 2/3
En la buena base 10, generalmente la escribimos como algo así
Cuando miramos esas representaciones, tendemos a asociar cada una de ellas con la fracción 2/3, aunque solo la primera representación es matemáticamente igual a la fracción. Las representaciones / aproximaciones segunda y tercera tienen un error del orden de 0.001, que en realidad es mucho peor que el error entre 9.2 y 9.1999999999999993. De hecho, ¡la segunda representación ni siquiera se redondea correctamente! Sin embargo, no tenemos un problema con 0.666 como una aproximación del número 2/3, por lo que realmente no deberíamos tener un problema con la aproximación de 9.2 en la mayoría de los programas . (Sí, en algunos programas es importante).
Bases de números
Así que aquí es donde las bases numéricas son cruciales. Si intentamos representar 2/3 en la base 3, entonces
En otras palabras, tenemos una representación exacta y finita para el mismo número cambiando de base. La conclusión es que, aunque puede convertir cualquier número a cualquier base, todos los números racionales tienen representaciones finitas exactas en algunas bases pero no en otras .
Para conducir este punto a casa, veamos 1/2. Puede sorprenderle que a pesar de que este número perfectamente simple tiene una representación exacta en la base 10 y 2, requiere una representación repetitiva en la base 3.
¿Por qué los números de coma flotante son inexactos?
Debido a que a menudo son aproximaciones racionales que no se pueden representar de manera finita en la base 2 (se repiten los dígitos), y en general se aproximan a números reales (posiblemente irracionales) que pueden no ser representables en muchos dígitos en una base.
fuente
1/3
igual que base-10 es perfecto para1/10
. Ninguna fracción funciona en base-2N
o un múltiplo de la misma.π
cancelen factores de etc.Si bien todas las otras respuestas son buenas, todavía falta una cosa:
Es imposible representar números irracionales (por ejemplo π,
sqrt(2)
,log(3)
, etc.), precisamente!Y esa es la razón por la que se les llama irracionales. Ninguna cantidad de almacenamiento de bits en el mundo sería suficiente para contener incluso uno de ellos. Solo la aritmética simbólica puede preservar su precisión.
Aunque si limitara sus necesidades matemáticas a números racionales, solo el problema de la precisión se vuelve manejable. Debería almacenar un par de enteros (posiblemente muy grandes)
a
yb
mantener el número representado por la fraccióna/b
. Toda su aritmética tendría que hacerse en fracciones al igual que en matemáticas de secundaria (por ejemploa/b * c/d = ac/bd
).Pero, por supuesto, todavía se encontraría con el mismo tipo de problemas cuando
pi
,sqrt
,log
,sin
, etc., están involucrados.TL; DR
Para la aritmética acelerada por hardware, solo se puede representar una cantidad limitada de números racionales. Cada número no representable es aproximado. Algunos números (es decir, irracionales) nunca pueden representarse sin importar el sistema.
fuente
Hay infinitos números reales (tantos que no puedes enumerarlos), y hay infinitos números racionales (es posible enumerarlos).
La representación de punto flotante es finita (como cualquier cosa en una computadora), por lo que inevitablemente muchos, muchos, muchos números son imposibles de representar. En particular, 64 bits solo le permiten distinguir entre solo 18,446,744,073,709,551,616 valores diferentes (que no es nada en comparación con el infinito). Con la convención estándar, 9.2 no es uno de ellos. Los que pueden son de la forma m.2 ^ e para algunos enteros mye.
Podría encontrar un sistema de numeración diferente, 10 basado, por ejemplo, donde 9.2 tendría una representación exacta. Pero otros números, digamos 1/3, aún serían imposibles de representar.
También tenga en cuenta que los números de coma flotante de doble precisión son extremadamente precisos. Pueden representar cualquier número en un rango muy amplio con hasta 15 dígitos exactos. Para los cálculos de la vida diaria, 4 o 5 dígitos son más que suficientes. Nunca necesitará esos 15, a menos que quiera contar cada milisegundo de su vida.
fuente
Los números de punto flotante son (simplificando ligeramente) un sistema de numeración posicional con un número restringido de dígitos y un punto de raíz móvil.
Una fracción solo se puede expresar exactamente usando un número finito de dígitos en un sistema de numeración posicional si los factores primos del denominador (cuando la fracción se expresa en sus términos más bajos) son factores de la base.
Los factores primos de 10 son 5 y 2, por lo que en la base 10 podemos representar cualquier fracción de la forma a / (2 b 5 c ).
Por otro lado, el único factor primo de 2 es 2, por lo que en la base 2 solo podemos representar fracciones de la forma a / (2 b )
Porque es un formato simple para trabajar y es lo suficientemente preciso para la mayoría de los propósitos. Básicamente, la misma razón por la que los científicos usan la "notación científica" y redondean sus resultados a un número razonable de dígitos en cada paso.
Sin duda sería posible definir un formato de fracción, con (por ejemplo) un numerador de 32 bits y un denominador de 32 bits. Sería capaz de representar números que el punto flotante de precisión doble IEEE no podría, pero igualmente habría muchos números que pueden representarse en punto flotante de precisión doble que no podrían representarse en un formato de fracción de tamaño fijo.
Sin embargo, el gran problema es que un formato de este tipo es difícil de hacer. Por dos razones.
Algunos idiomas ofrecen tipos de fracciones, pero generalmente lo hacen en combinación con precisión arbitraria, esto evita tener que preocuparse por aproximar fracciones pero crea su propio problema, cuando un número pasa a través de una gran cantidad de pasos de cálculo del tamaño del denominador y por lo tanto, el almacenamiento necesario para la fracción puede explotar.
Algunos idiomas también ofrecen tipos de coma flotante decimal, estos se utilizan principalmente en escenarios en los que es importante que los resultados que obtiene la computadora coincidan con las reglas de redondeo preexistentes que se escribieron teniendo en cuenta a los humanos (principalmente cálculos financieros). Estos son un poco más difíciles de trabajar que el punto flotante binario, pero el mayor problema es que la mayoría de las computadoras no ofrecen soporte de hardware para ellos.
fuente
Prueba esto
'
decimalValue
' es su valor para convertir.fuente