functions - realfrac haskell
Rangos y carrozas Haskell (2)
La sintaxis [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
eInteger
, 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
, ese2 − 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 sie1 > e3
.- La secuencia
enumFromThenTo e1 e2 e3
es la lista[e1,e1 + i,e1 + 2i,…e3]
, donde el incremento,i
, ese2 − e1
. Si el incremento es positivo o cero, la lista finaliza cuando el siguiente elemento sea mayor quee3
; la lista está vacía sie1 > e3
. Si el incremento es negativo, la lista finaliza cuando el siguiente elemento sea menor quee3
; la lista está vacía sie1 < e3
.
Esto es más o menos lo que esperaría, pero las instancias Float
y Double
se definen de manera diferente:
Para
Float
yDouble
, la semántica de la familiaenumFrom
viene dada por las reglas paraInt
anteriores, excepto que la lista finaliza cuando los elementos superane3 + i∕2
para el incremento positivoi
, o cuando son menores quee3 + i∕2
para negativoi
.
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]
¿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.
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 f l r s = sum [ f x | 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 suma, porque el último valor de x
resulta ser 3.000000 algo. Podemos simular esto
bad_trIntegrate f l r s = sum [ f x | x<-[l,(l+s)..(r-s)] ] * 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 programador 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.