The simple answer
To understand this behaviour, you need to know that the expression [a,b..c]
will be desugared into enumFromThenTo a b c
where enumFromThenTo
is a method of the Enum
class.
The Haskell standard says that
For Float
and Double
, the semantics of the enumFrom
family is given by the rules for Int
above, except that the list terminates when the elements become greater than e3 + i∕2
for positive increment i
, or when they become less than e3 + i∕2
for negative i
.
Standards are standards, after all. But that's not very satisfactory.
Going deeper
The Double
instance of Enum
is defined in the module GHC.Float, so let's look there. We find:
instance Enum Double where
enumFromThenTo = numericFromThenTo
That's not incredibly helpful, but a quick Google search reveals that numericFromThenTo
is defined in GHC.Real, so let's go there:
numericEnumFromThenTo e1 e2 e3 = takeWhile pred (numericEnumFromThen e1 e2)
where
mid = (e2 - e1) / 2
pred | e2 >= e1 = (<= e3 + mid)
| otherwise = (>= e3 + mid)
That's a bit better. If we assume a sensible definition of numericEnumFromThen
, then calling
numericEnumFromThenTo 0.1 0.3 1.0
will result in
takeWhile pred [0.1, 0.3, 0.5, 0.7, 0.9, 1.1, 1.3 ...]
Since e2 > e1
, the definition of pred
is
pred = (<= e3 + mid)
where
mid = (e2 - e1) / 2
Therefore we will take elements (call them x
) from this list as long as they satisfy x <= e3 + mid
. Let's ask GHCi what that value is:
>> let (e1, e2, e3) = (0.1, 0.3, 1.0)
>> let mid = (e2 - e1) / 2
>> e3 + mid
1.1
That's why you see 1.09999...
in the list of results.
The reason that you see 1.0999...
instead of 1.1
is because 1.1
is not representable exactly in binary.
The reasoning
Why would the standard prescribe such bizarre behaviour? Well, consider what might happen if you only took numbers that satisfied (<= e3)
. Because of floating point error or nonrepresentability, e3
may never appear in the list of generated numbers at all, which could mean that innocuous expressions like
[0.0,0.02 .. 0.1]
would result in
[0.0, 0.02, 0.04, 0.06, 0.08]
which seems a little bizarre. Because of the correction in numericFromThenTo
, we make sure that we get the expected result for this (presumably more common) use case.