RFC: Fixing floating point conversions.

Currently, Haskell does not provide any mechanism for converting one floating type to another, since realToFrac appears to be fundamentally broken in this regard. For details on that matter, see the discussion at http://hackage.haskell.org/trac/ghc/ticket/3676. Essentially, realToFrac does not preserve floating values which are not representable as a Rational. I'd like to start discussing improvements to this situation by submitting some ideas which I have considered for comments. First though, two proposals: * Codify a requirement that Double values are a superset of Float values. C demands this, as it makes it possible to define Float to Double conversions as value-preserving. I couldn't find such a statement in the report; idea #1 below requires this. * Define toRational as throwing an exception on non-finite input. Infinities magically turning into integers is simply not nice. I personally feel that floating types should not be instances of the Real class, but that discussion can be saved for another day. *** Idea #0 *** Fix realToFrac. This seems to be impossible without highly undesirable consequences, see the trac ticket linked above for details. *** Idea #1 *** Add two methods to the RealFloat class: toDouble :: RealFloat a => a -> Double fromDouble :: RealFloat a => Double -> a and a function: toFloating :: (RealFloat a, RealFloat b) => a -> b toFloating = fromDouble . toDouble Advantages: * No extensions (other than this one) beyond Haskell 98 are required. * Simple to define instances, exactly two functions per floating type. * Trivial to implement. Disadvantages: * It encodes directly into the RealFloat class the knowledge that Double can represent values of any other floating type. This makes it difficult or impossible to create new floating types later. *** Idea #2 *** Similar to #1, except using a "generic" type instead of Double. Define a new type, call it FloatConvert, which represents "rational plus other values". Something along the lines of: data FloatConvert = FCZero Bool -- Signed zero | FCInfinity Bool -- Signed infinity | FCNaN Integer -- Generic NaN | FCFinite Rational -- Finite, non-zero value Add two new methods to the RealFloat class: toFloatConvert :: RealFloat a => a -> FloatConvert fromFloatConvert :: RealFloat a => FloatConvert -> a and a function: toFloating :: (RealFloat a, RealFloat b) => a -> b toFloating = fromFloatConvert . toFloatConvert Advantages: * No extensions (other than this one) beyond Haskell 98 are required. * Simple to define instances, exactly two functions per floating type. * Easy to add floating types to the language, and easy for users to define their own in libraries. Disadvantages: * A data type whose sole purpose is to convert floating types seems like a wart. * While the free-form encoding of NaN values will allow conversion from a type to itself to be the identity function, it may make it tricky to perform the "ideal" conversion between different types. *** Idea #3 *** Use a multi-parameter type class: class FloatConvert a b where toFloating :: a -> b Advantages: * Can define any conversion imaginable without constraints. * Straightforward to add floating types to the language. Disadvantages: * Requires multi-parameter type classes. * Not practical for library authors to define their own instances of this class except in special circumstances, since it requires knowledge of all other floating types. Of these three ideas, I think #2 (or something similar) is the most workable. What do others think? -- Nick Bowler, Elliptic Technologies (http://www.elliptictech.com/)

Nick Bowler wrote:
*** Idea #3 ***
Use a multi-parameter type class:
We couldn't go down this route until MPTCs themselves are added to the language, which I think is unlikely to be soon as the whole fundeps/type families issue is still unresolved. Ganesh =============================================================================== Please access the attached hyperlink for an important electronic communications disclaimer: http://www.credit-suisse.com/legal/en/disclaimer_email_ib.html ===============================================================================

Nick Bowler schrieb:
*** Idea #2 ***
Similar to #1, except using a "generic" type instead of Double.
Define a new type, call it FloatConvert, which represents "rational plus other values". Something along the lines of:
data FloatConvert = FCZero Bool -- Signed zero | FCInfinity Bool -- Signed infinity | FCNaN Integer -- Generic NaN | FCFinite Rational -- Finite, non-zero value
interesting. What is the Integer in FCNaN for?
Add two new methods to the RealFloat class:
toFloatConvert :: RealFloat a => a -> FloatConvert fromFloatConvert :: RealFloat a => FloatConvert -> a
and a function:
toFloating :: (RealFloat a, RealFloat b) => a -> b toFloating = fromFloatConvert . toFloatConvert
Advantages: * No extensions (other than this one) beyond Haskell 98 are required. * Simple to define instances, exactly two functions per floating type. * Easy to add floating types to the language, and easy for users to define their own in libraries.
Disadvantages: * A data type whose sole purpose is to convert floating types seems like a wart. * While the free-form encoding of NaN values will allow conversion from a type to itself to be the identity function, it may make it tricky to perform the "ideal" conversion between different types.
I don't understand this last point about "free-form encoding of NaN" I would come up with a data type like: data ExtNum a = NegativeZero | NaN | Infinity Bool | Num a add instances for the classes, Eq, Ord, Num, .... (depending on "a" that must be at least from the class Num for "0") and use "ExtNum Rational" for floating point conversions. Cheers Christian

On 17:30 Thu 25 Feb , Christian Maeder wrote:
Nick Bowler schrieb:
*** Idea #2 ***
Similar to #1, except using a "generic" type instead of Double.
Define a new type, call it FloatConvert, which represents "rational plus other values". Something along the lines of:
data FloatConvert = FCZero Bool -- Signed zero | FCInfinity Bool -- Signed infinity | FCNaN Integer -- Generic NaN | FCFinite Rational -- Finite, non-zero value
interesting. What is the Integer in FCNaN for?
Many floating point formats have multiple NaNs. In the IEEE 754 binary formats, a NaN is specified by a maximum exponent and *any* non-zero significand. The extra bits are sometimes used for diagnostic information. There are signaling and quiet NaNs, and they have a sign bit. IEEE 754 recommends that operations involving NaNs preserve as much of this information as possible. I chose Integer since it can encode all of this information. It is desirable for conversions from a type to itself to be the identity function, even in the presence of multiple NaNs. I'm sure many other encodings are workable.
* While the free-form encoding of NaN values will allow conversion from a type to itself to be the identity function, it may make it tricky to perform the "ideal" conversion between different types.
I don't understand this last point about "free-form encoding of NaN"
It's free-form in that, as I specified it, it's up to the particular RealFloat instance to decide how the Integer is used. This might make conversions which preserve, say, signaling NaNs trickier to implement.
I would come up with a data type like:
data ExtNum a = NegativeZero | NaN | Infinity Bool | Num a
add instances for the classes, Eq, Ord, Num, .... (depending on "a" that must be at least from the class Num for "0")
and use "ExtNum Rational" for floating point conversions.
-- Nick Bowler, Elliptic Technologies (http://www.elliptictech.com/)

On Thu, Feb 25, 2010 at 10:40:48AM -0500, Nick Bowler wrote:
*** Idea #1 ***
Add two methods to the RealFloat class:
toDouble :: RealFloat a => a -> Double fromDouble :: RealFloat a => Double -> a
and a function:
toFloating :: (RealFloat a, RealFloat b) => a -> b toFloating = fromDouble . toDouble
That is exactly how I solved it in the jhc libraries. Though, I may switch to using a 'FloatMax' type that is always aliased to the largest floating point type available, now that Float128 and Float80 are potentially available. (but it would just be an alias for Double for now) John -- John Meacham - ⑆repetae.net⑆john⑈ - http://notanumber.net/

On 14:23 Thu 25 Feb , John Meacham wrote:
On Thu, Feb 25, 2010 at 10:40:48AM -0500, Nick Bowler wrote:
*** Idea #1 ***
Add two methods to the RealFloat class:
toDouble :: RealFloat a => a -> Double fromDouble :: RealFloat a => Double -> a
and a function:
toFloating :: (RealFloat a, RealFloat b) => a -> b toFloating = fromDouble . toDouble
That is exactly how I solved it in the jhc libraries.
Though, I may switch to using a 'FloatMax' type that is always aliased to the largest floating point type available, now that Float128 and Float80 are potentially available. (but it would just be an alias for Double for now)
Indeed, a type alias sounds like a good improvement to idea #1 for exactly the reasons that you mention. However, for an actual amendment to the standard, I'd like to see a solution which allows libraries to define their own instances of the numeric classes. Imagine that someone wants to create a library which implements decimal floating point arithmetic. With something like toDouble or toFloatMax, the author is faced with two major obstacles: * After creating new types, 'FloatMax' (which can't be redefined) might not be the "max" anymore. * When 'FloatMax' has a different radix, there is going to be loss of information when converting to it. For example, 1/5 is exactly representable in decimal floating point, but not in binary floating point with any finite number of significand digits. The end result is that 'toFloating' is not suitable for the decimal floating point library, so the author would have to invent her own conversion method. It is for this reason that I prefer something along the lines of idea #2 with a "generic" intermediate type. Nevertheless, #1 (with or without FloatMax) is still a /huge/ improvement over the status quo. -- Nick Bowler, Elliptic Technologies (http://www.elliptictech.com/)
participants (4)
-
Christian Maeder
-
John Meacham
-
Nick Bowler
-
Sittampalam, Ganesh