Rangos y carrozas Haskell

¿Por qué el comportamiento de la notación de rango Haskell es diferente para los flotantes que para los enteros y los caracteres?

Prelude> [1, 3 .. 10] :: [Int] [1,3,5,7,9] Prelude> [1, 3 .. 10] :: [Float] [1.0,3.0,5.0,7.0,9.0,11.0] Prelude> ['a', 'c' .. 'f'] "ace" 

Lo entendería si el último elemento estuviera cerca del límite superior, pero obviamente este no es un problema de redondeo.

La syntax [e1, e2 .. e3] es realmente azúcar sintáctica para enumFromThenTo e1 e2 e3 , que es una función en la clase de tipo Enum .

El estándar Haskell define su semántica de la siguiente manera:

Para los tipos Int e Integer , las funciones de enumeración tienen el siguiente significado:

  • La secuencia enumFrom e1 es la lista [e1,e1 + 1,e1 + 2,…] .
  • La secuencia enumFromThen e1 e2 es la lista [e1,e1 + i,e1 + 2i,…] , donde el incremento, i , es e2 − e1 . El incremento puede ser cero o negativo. Si el incremento es cero, todos los elementos de la lista son iguales.
  • La secuencia enumFromTo e1 e3 es la lista [e1,e1 + 1,e1 + 2,…e3] . La lista está vacía si e1 > e3 .
  • La secuencia enumFromThenTo e1 e2 e3 es la lista [e1,e1 + i,e1 + 2i,…e3] , donde el incremento, i , es e2 − e1 . Si el incremento es positivo o cero, la lista finaliza cuando el siguiente elemento sea mayor que e3 ; la lista está vacía si e1 > e3 . Si el incremento es negativo, la lista finaliza cuando el siguiente elemento sea menor que e3 ; la lista está vacía si e1 < e3 .

Esto es más o menos lo que esperaría, pero las instancias Float y Double se definen de manera diferente:

Para Float y Double , la semántica de la familia enumFrom viene dada por las reglas para Int anteriores, excepto que la lista finaliza cuando los elementos superan e3 + i∕2 para el incremento positivo i , o cuando son menores que e3 + i∕2 para negativo i .

No estoy seguro de cuál es la justificación para esto, así que la única respuesta que puedo darte es que es así porque está definido de esa manera en el estándar.

Puede solucionar esto enumerando utilizando enteros y convirtiendo a Float después.

 Prelude> map fromIntegral [1, 3 .. 10] :: [Float] [1.0,3.0,5.0,7.0,9.0] 

Ok, @Henning Makholm ya dijo esto en su comentario, pero no explicó por qué esta es realmente una mejor solución.

Lo primero que hay que decir: cuando se trata de punto flotante, siempre debemos tener en cuenta los posibles errores de redondeo. Cuando escribimos [0.0, 0.1 .. 1.0] debemos tener en cuenta que todos estos números, excepto el primero, no estarán en los lugares exactos de las décimas. Donde necesitamos este tipo de certeza, no debemos usar flotadores en absoluto.

Pero, por supuesto, hay muchas aplicaciones donde nos contentamos con certeza razonable , pero necesitamos alta velocidad. Ahí es donde las carrozas son geniales. Una posible aplicación de dicha lista sería una simple integración numérica trapezoidal:

 trIntegrate flrs = sum [ fx | x<-[l,(l+s)..r] ] * s - (f(l)+f(r))*s/2 

trIntegrate ( \x -> exp(x + cos(sqrt(x) - x*x)) ) 1.0 3.0 0.1 esto: trIntegrate ( \x -> exp(x + cos(sqrt(x) - x*x)) ) 1.0 3.0 0.1 => 25.797334337026466
comparado con 25.9144 un error de menos del uno por ciento. No es exacto, por supuesto, pero eso es inherente al método de integración.

Supongamos ahora que los rangos de flotación se definieron para terminar siempre al cruzar el borde derecho. Entonces, sería posible (¡pero no podemos estar seguros al respecto!) Que solo se calculan 20 valores en lugar de 21 en la sum, porque el último valor de x resulta ser 3.000000 algo. Podemos simular esto

 bad_trIntegrate flrs = sum [ fx | x<-[l,(l+s)..(rs)] ] * s - (f(l)+f(r))*s/2 

entonces obtenemos

 bad_trIntegrate ( \x -> exp(x + cos(sqrt(x) - x*x)) ) 1.0 3.0 0.1 

=> 21.27550564546988
urgh!

Esto no tiene nada que ver con ocultar los problemas con el punto flotante. Es solo un método para ayudar al progtwigdor a resolver estos problemas más fácilmente. De hecho, el resultado contrario a la intuición de [1, 3 .. 10] :: Float ayuda a recordar estos problemas.

Intereting Posts