RFC: Fixing floating point conversions.

Nick Bowler nbowler at elliptictech.com
Thu Feb 25 10:40:48 EST 2010


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/)


More information about the Haskell-prime mailing list