[Haskell-cafe] RFC: decodeFloat

Daniel Fischer daniel.is.fischer at googlemail.com
Fri Aug 26 22:08:25 CEST 2011


Occasionally, the behaviour of decodeFloat and its consequences causes 
concern and/or bug reports (e.g. 
http://hackage.haskell.org/trac/ghc/ticket/3898).

The main problem is the treatment of NaNs and infinities (that it doesn't 
distinguish between 0.0 and -0.0 is a minor thing), which are converted as 
if they were ordinary finite values.

Thus, for example, decodeFloat (1/0 :: Double) = (2^52,972) and 
consequently

floor (1/0 :: Double) = 2^1024 (corresponding for round, truncate, ceiling, 
properFraction),
significand (1/0 :: Double) = 0.5,
Prelude> uncurry encodeFloat (decodeFloat (1/0 :: Float)) :: Double
3.402823669209385e38

and similar meaningless/nonsensical results for NaNs.

At its type, decodeFloat :: RealFloat a => a -> (Integer, Int), I see only 
two reasonable options,

1. leave the behaviour as it is, just warn about the exceptional cases in 
the documentation,

2. let decodeFloat raise an error when its argument is a NaN or infinite.

Both options have disadvantages, 1. makes it easy to get meaningless 
results when there's a non-finite value around, 2. incurs a nontrivial 
performance penalty.

Paying that performance penalty when dealing only with known-to-be-good 
values is undesirable, but so are meaningless results.


A third option would be providing both behaviours by adding a function.
That could take several forms,

a) leave decodeFloat as is and add safeDecodeFloat outside the RealFloat 
class, that would check the value first and then either raise an error on 
NaN/Infinity or return a Maybe (Integer, Int),

b) add a function to the RealFloat class, either leave decodeFloat as is 
and add safeDecodeFloat with the behaviour as in a) or change decodeFloat 
according to 2. and add unsafeDecodeFloat with the current behaviour.

The drawback of a) is that safeDecodeFloat would have to perform two 
checks, while with IEEE Doubles/Floats, the result could be determined by 
one check (which would also be simpler than each of isNaN and isInfinite).
Also, which module should export it?

The drawback of b) is adding a function to RealFloat, as if that wasn't big 
enough already. Since both behaviours allow a default definition in terms 
of the other, only code that by chance uses the new name would break.

What would be the community's preferred way to handle this issue?

Cheers,
Daniel



More information about the Haskell-Cafe mailing list