2010-01-10 24 views
8

He estado yendo a través de un tutorial Haskell recientemente y se dio cuenta este comportamiento cuando se trata de algunas expresiones simples Haskell en el ghci shell interactivo:¿Por qué ghci dice que 1.1 + 1.1 + 1.1> 3.3 es verdadero?

Prelude> 1.1 + 1.1 == 2.2 
True 
Prelude> 1.1 + 1.1 + 1.1 == 3.3 
False 
Prelude> 1.1 + 1.1 + 1.1 > 3.3 
True 
Prelude> 1.1 + 1.1 + 1.1 
3.3000000000000003 

¿Alguien sabe por qué es así?

+0

Agradable javascript float - decimal - visualizador binario: http://babbage.cs.qc.edu/IEEE-754/Decimal.html – Seth

+4

Este no es un problema específico de Haskell: lo mismo sucederá en cualquier idioma que usa números de coma flotante. Los detalles precisos dependen de la implementación del punto flotante, pero la mayoría de los procesadores usan IEEE-754, que está estrechamente especificado para garantizar que los programas se comporten de la misma manera en todas partes. –

Respuesta

36

Debido 1.1 y 3.3 son floating point numbers. Las fracciones decimales, como .1 o .3, no son exactamente representables en un número de punto flotante binario. .1 significa 1/10. Para representar eso en binario, donde cada dígito fraccionario representa 1/2 n (1/2, 1/4, 1/8, etc.), necesitaría un número infinito de dígitos, 0.000110011 ... repitiéndose infinitamente.

Este es exactamente el mismo problema que representar, por ejemplo, 1/3 en la base 10. En la base 10, necesitarías un número infinito de dígitos, .33333 ... para siempre, para representar 1/3 exactamente. Así que trabajando en la base 10, usualmente redondeas, a algo así como .33. Pero si suma tres copias de eso, obtiene .99, no 1.

Para obtener más información sobre el tema, lea What Every Computer Scientist Should Know About Floating Point Arithmetic.

Para representar números racionales con mayor precisión en Haskell, siempre puede usar el tipo de datos racional, Ratio; junto con bignums (enteros arbitrariamente grandes, Integer en Haskell, a diferencia de Int que son de tamaño fijo) como el tipo de numerador y denominador, puede representar números racionales arbitrariamente precisos, pero a una velocidad significativamente más lenta que los números de coma flotante, que son implementado en hardware y optimizado para la velocidad.

Los números de coma flotante son una optimización, para cálculo científico y numérico, que intercambia precisión para alta velocidad, lo que le permite realizar una gran cantidad de cálculos en poco tiempo, siempre que sepa redondear y cómo afecta tus cálculos.

+0

¡Gracias por indicar el tipo de datos 'Ratio'! –

+0

Sólo una objeción, pero 'Ratio' es un constructor de tipos:' Rational' es un tipo, el tipo (alias) de 'Ratio Integer'. – BMeph

+0

@BMeph: Ah sí, cierto. Mientras tanto, me he familiarizado más con Haskell (desde que hice esta pregunta) y los constructores de tipo vs. tipos ya no me confunden cuando tienen el mismo nombre. –

5

Pocas flotadores se pueden expresar con exactitud mediante IEEE representación 754, por lo que siempre habrá un poco apagado.

13

Debido a que los números de coma flotante no son exactos (wikipedia)

+8

-1 Engañoso, lo siento. Los números de coma flotante IEEE * son perfectamente precisos *, solo tiene que prestar atención a los errores de redondeo con números decimales. Usted tiene los mismos problemas tratando de representar fracciones como 1/3 en decimal (0.3333 ...). – Seth

+5

De acuerdo. El problema no es de precisión, sino de representación. Los números que pueden representarse completamente son completamente precisos. Los que se encuentran entre esos números solo pueden ser aproximados, y ese es el problema. – codekaizen

+16

Entonces, ¿qué es lo que ustedes dos dicen los nitpickers es que los números de punto flotante son solo inexactos cuando son inexactos? Wow, nada se le pasa a la gente. Si '1.1 * 3! = 3.3' en su sistema matemático, es * no * exacto. – Chuck

6

Tiene que ver con la forma en que funcionan los números flotantes IEEE.

1.1 se representa como 1.1000000000000001 en coma flotante, 3.3 se representa como 3.2999999999999998.

Así 1.1 + 1.1 + 1.1 es en realidad

1.1000000000000001 + 1.1000000000000001 + 1.1000000000000001 = 3.3000000000000003

Lo cual, como se puede ver es en realidad más grande que 3.2999999999999998.

La solución habitual es o bien no evaluar la igualdad, o para comprobar si un número está dentro del objetivo +/- un pequeño epsilon (que define la precisión que necesita).

Ej .: si ambos son verdaderos, entonces la suma es "igual" a 3.3 (dentro del error permitido).

1.1 + 1.1 + 1.1 < 3.3 + 1e9 
1.1 + 1.1 + 1.1 > 3.3 - 1e9 
+9

No, 1.1 no está representado como 1.1000000000000001. Se representa como 1,0001100110011001100110011001100110011001100110011010 (base 2), que cuando se convierte a decimal, se redondea a 1.100000000000000001 –

+0

Ahh, es cierto. – Seth

13

usted puede evitar los errores de punto flotante en Haskell usando tipos racionales:

Prelude Data.Ratio> let a = (11 % 10) + (11 % 10) + (11 % 10) 
Prelude Data.Ratio> a > (33 % 10) 
False 
Prelude Data.Ratio> fromRational a 
3.3 

Por supuesto, paga una multa de rendimiento para el aumento de la precisión.

+8

Incluso puede escribir 'let a = 1.1 + 1.1 + 1.1 :: Rational', sin preocuparse por la pérdida de precisión (el literal '1.1' es en realidad una abreviatura de' fromRational (11% 10) '). –

+0

De hecho, simplemente puede escribir '1.1 + 1.1 + 1.1 <(3.3 :: Rational)' para hacer exactamente lo que OP quiere. Es un tanto no canónico que ghci omite de todos modos que este cálculo flote, cuando no se dice explícitamente '1.1 + 1.1 + 1.1 <(3.3 :: Flotante)'. – leftaroundabout

1

En general, no debe comparar los flotadores para la igualdad (por los motivos descritos anteriormente). La única razón por la que puedo pensar es si quieres decir "¿ha cambiado este valor?" Por ejemplo, "if (newscore/= oldscore)" luego tome alguna medida. Eso está bien, siempre y cuando no compares el resultado de dos cálculos separados para comprobar si son iguales (porque incluso matemáticamente si lo son, podrían redondear de otro modo).

Cuestiones relacionadas