2008-10-07 10 views

Respuesta

54

Estos accuracy problems se deben a internal representation de números en coma flotante y no hay mucho que pueda hacer para evitarlo.

Por cierto, la impresión de estos valores en tiempo de ejecución a menudo todavía conduce a los resultados correctos, al menos con los modernos compiladores de C++. Para la mayoría de las operaciones, esto no es un gran problema.

+0

Es algo que los programadores deben tener en cuenta sin embargo, especialmente si trabajan con números muy grandes o muy pequeños donde la precisión puede ser importante. – tloach

+0

No necesariamente muy grande o muy pequeño: la precisión de coma flotante es la misma independientemente del tamaño total del número. El problema es cuando * mezcla * valores muy grandes y muy pequeños, como sumarlos. –

+4

Oscuro: eso no es cierto en realidad. El espacio de valores representables es mucho más denso cerca de 0, y mucho más disperso a medida que se va hacia el infinito (por ejemplo, 2^24 + 1 no se puede representar exactamente utilizando el estándar de punto flotante IEEE para dobles de 32 bits) – SquareCog

9

Si usted tiene un valor como:

double theta = 21.4; 

Y que quieres hacer:

if (theta == 21.4) 
{ 
} 

Tienes que ser un poco inteligente, tendrá que comprobar si el valor de theta es realmente cerca de 21.4, pero no necesariamente ese valor.

if (fabs(theta - 21.4) <= 1e-6) 
{ 
} 
+1

double theta = 21,4; bool b = theta == 21.4; // aquí b es siempre cierto –

3

Una manera de evitar esto es utilizar una biblioteca que utiliza un método alternativo de representar números decimales, como BCD

+0

Existen mejores técnicas que BCD. –

+1

Hubiera sido bueno decir una o dos de esas técnicas. –

+0

Lo sentimos: aritmética de precisión arbitraria: ver http://gmplib.org/ por ejemplo. –

2

Si está utilizando Java y necesita precisión, utilice la clase BigDecimal para cálculos de punto flotante. Es más lento pero más seguro.

+0

Quiere decir * en lugar de * coma flotante. – EJP

36

me gusta Joel's explanation, que trata de un problema de precisión de punto flotante binario similar en Excel 2007:

Vea cómo hay una gran cantidad de 0110 0110 0110 allí al final? Eso es porque 0.1 tiene sin representación exacta en el código binario ... es un número binario repetitivo. Es algo así como que 1/3 no tiene representación en decimal. 1/3 es 0.33333333 y tienes que seguir escribiendo 3 para siempre. Si pierdes la paciencia, obtienes algo inexacto.

Así que puedes imaginar cómo, en decimal, si tratas de hacer 3 * 1/3, y no tienes tiempo para escribir 3 para siempre, el resultado que obtendrías sería 0.99999999, no 1, y las personas se enojaría contigo por estar equivocado.

+7

Si intentaste hacer 3 * 1/3, multiplicarías los tres por uno y obtendrás tres. Luego dividirías tres por tres y nadie debería estar enojado. Supongo que Joel quería decir 3 * (1/3). – Nosredna

+2

@Nosredna Depende de si el idioma que está utilizando tiene una precedencia de operador mayor para '*' o '/'. –

5

Utilice el tipo de punto fijo decimal si desea estabilidad en los límites de precisión. Hay gastos generales, y debe emitir explícitamente si desea convertir a punto flotante. Si lo convierte a punto flotante volverá a introducir las inestabilidades que parecen molestarlo.

Alternativamente, puede superarlo y aprender a trabajar con la precisión limitada de la aritmética de coma flotante. Por ejemplo, puede usar el redondeo para hacer que los valores converjan, o puede usar comparaciones épsilon para describir una tolerancia. "Epsilon" es una constante que configura que define una tolerancia. Por ejemplo, puede optar por considerar que dos valores son iguales si están dentro de 0,0001 entre sí.

Se me ocurre que puede usar la sobrecarga del operador para hacer las comparaciones épsilon transparentes.Eso sería genial.


Para las representaciones de mantissa-exponente EPSILON se debe calcular para que permanezca dentro de la precisión representable. Para un número N, Epsilon = N/10E + 14

System.Double.Epsilon es el valor positivo representable más pequeño para el tipo Double. Es también pequeño para nuestro propósito. Lea Microsoft's advice on equality testing

+0

Nota rápida (pero no es una contradicción): si usa el tipo System.Decimal en .NET, tenga en cuenta que todavía es un tipo de coma flotante. Es un punto decimal flotante, pero sigue siendo un punto flotante. Ah, y también ten cuidado con System.Double.Epsilon, ya que no es lo que esperas que sea :) –

7

Esto es parcialmente específico de la plataforma, y ​​no sabemos qué plataforma está utilizando.

También es en parte un caso de saber lo que realmente quiere para ver. El depurador le muestra, en cierta medida, de todos modos, el valor preciso almacenado en su variable. En mi article on binary floating point numbers in .NET, hay un C# class que le permite ver absolutamente número exacto almacenado en un doble. La versión en línea no funciona en este momento. Trataré de poner una en otro sitio.

Dado que el depurador ve el valor "real", tiene que hacer una llamada de juicio sobre qué mostrar; podría mostrarle el valor redondeado a algunos lugares decimales, o un valor más preciso. Algunos depuradores hacen un mejor trabajo que otros al leer las mentes de los desarrolladores, pero es un problema fundamental con los números de coma flotante binarios.

+2

Jon, la pregunta fue originalmente etiquetada como C++/VC6, así que * conocíamos * la plataforma antes de que alguien decidiera que esta información no era importante y editó las etiquetas. –

3

Me parece que 21.399999618530273 es una sola precisión (flotación) la representación de 21.4. Parece que el depurador está bajando desde el doble para flotar en algún lugar.

2

No puedes evitar esto como que está utilizando números de punto flotante con una cantidad fija de bytes. Simplemente no hay isomorfismo posible entre los números reales y su notación limitada.

Pero la mayoría de las veces simplemente puede ignorarlo. 21.4 == 21.4 seguiría siendo cierto porque sigue siendo los mismos números con el mismo error. Pero 21.4f == 21.4 puede no ser cierto porque el error para float y double es diferente.

Si necesita una precisión fija, tal vez debería probar los números de punto fijo. O incluso enteros. Yo, por ejemplo, a menudo uso int (1000 * x) para pasar al depurador.

+0

Uno podría preferir int (1000 * x + .5) para hacer que 21.4 aparezca como se esperaba. – Reunanen

4

He encontrado esto antes (on my blog) - Creo que la sorpresa tiende a ser que los números "irracionales" son diferentes.

Por 'irracional' aquí me refiero al hecho de que no se pueden representar con precisión en este formato. Los números irracionales reales (como π - pi) no se pueden representar con exactitud en absoluto.

La mayoría de la gente está familiarizada con 1/3 que no trabaja en decimal: 0.3333333333333 ...

Lo curioso es que 1.1 no funciona en balsas. La gente espera que los valores decimales funcionen en números de coma flotante debido a cómo piensan en ellos:

1.1 es de 11 x 10^-1

Cuando en realidad están en la base-2

1,1 es 154811237190861 x 2^-47

No puede evitarlo, solo tienes que acostumbrarte al hecho de que algunas carrozas son "irracionales", del mismo modo que 1/3.

+1

Keith, en realidad ninguno de sus ejemplos es irracional. Sqrt (2) es irracional, PI es irracional, pero cualquier número entero dividido por un entero es, por definición, racional. – Sklivvz

+0

Tiene toda la razón, de ahí las comillas simples. En la teoría de las matemáticas, estos son números racionales, simplemente no se pueden expresar en el mecanismo de almacenamiento utilizado. – Keith

+0

Respuesta modificada para que se borre, gracias Sklivvz. – Keith

0

Según el javadoc

"Si al menos uno de los operandos a un operador numérico es de tipo doble, entonces la operación
se lleva a cabo utilizando la aritmética de coma flotante de 64 bits, y el resultado de el operador numérico
es un valor de tipo double. Si el otro operando no es un doble, es
primero ampliado (§5.1.5) para escribir double por promoción numérica (§5.6). "

Here is the Source

Cuestiones relacionadas