
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

On Fri, Aug 26, 2011 at 10:08:25PM +0200, Daniel Fischer wrote:
a nontrivial performance penalty.
It would be nice if the current function, without performance penalty, were ultimately renamed to unsafeDecodeFloat. A new function could return something like data DecodedFloat = Decoded Integer Int | NaN | Infinity | NegativeInfinity | NegativeZero (I'm not sure which constructors you'd want exactly). Thanks Ian

On Fri, Aug 26, 2011 at 16:29, Ian Lynagh
A new function could return something like data DecodedFloat = Decoded Integer Int | NaN | Infinity | NegativeInfinity | NegativeZero
Users who need strict IEE754 compatibility passing a NaN through will require the NaN constructor to return a raw representation of the NaN. -- brandon s allbery allbery.b@gmail.com wandering unix systems administrator (available) (412) 475-9364 vm/sms

On Friday 26 August 2011, 22:29:20, Ian Lynagh wrote:
On Fri, Aug 26, 2011 at 10:08:25PM +0200, Daniel Fischer wrote:
a nontrivial performance penalty.
It would be nice if the current function, without performance penalty, were ultimately renamed to unsafeDecodeFloat.
Yes, however, removing decodeFloat or changing its type would break existing code. I have no idea how much, so I'm not sure whether renaming the current function and giving decodeFloat a new type is feasible. I suspect it's not. So then we'd have unsafeDecodeFloat -- current safeDecodeFloat -- new, safe decodeFloat -- what to do with that? And what would we do with significand/exponent on NaN/Infinity? error?
A new function could return something like data DecodedFloat = Decoded Integer Int
| NaN | Infinity | NegativeInfinity | NegativeZero
(I'm not sure which constructors you'd want exactly).
Something like that. I'm pondering something more general, though. data RealRep = I Integer | R Rational | Bin Integer Int -- m*2^e | Dec Integer Int -- m*10^e | NaN ? -- not sure how to treat different NaNs | Infinity | NegativeInfinity | NegativeZero -- possibly more to represent all (usual) types of Real numbers. Using that as intermediate type instead of Rational in realToFrac would allow a correct Double <-> Float (and newtypes) conversion also without rewrite rules (and should be faster than 'fromRational . toRational' for these, since it would be basically 'uncurry encodeFloat . decodeFloat'). Using that instead of Rational in reading would allow curing http://hackage.haskell.org/trac/ghc/ticket/3897 (although that probably isn't a real life problem; and it could well be slower for the average input string). And we could get rid of infinity = 1 :% 0 notANumber = 0 :% 0. But it would be a *huge* change.

On 8/26/11 7:24 PM, Daniel Fischer wrote:
Using that as intermediate type instead of Rational in realToFrac would allow a correct Double<-> Float (and newtypes) conversion also without rewrite rules (and should be faster than 'fromRational . toRational' for these, since it would be basically 'uncurry encodeFloat . decodeFloat').
For these issues I'd like to point out the RealToFrac class[1] for correcting the problems with the Prelude's realToFrac. The current version uses -XOverlappingInstances to provide general instances, but that's just a convenience and not strictly necessary. The only thing it actually requires is MPTCs. I've been waiting for the haskell' committee to finally get around to accepting MPTCs before suggesting the class replace the Prelude's function. [1] http://hackage.haskell.org/packages/archive/logfloat/0.12.1/doc/html/Data-Nu... -- Live well, ~wren
participants (4)
-
Brandon Allbery
-
Daniel Fischer
-
Ian Lynagh
-
wren ng thornton