As far as I can tell, the only way to convert between floating types in Haskell is to use realToFrac. Unfortunately, this function does some insane mangling of values when converting. Some examples:
The last item illustrates an important point: it is impossible to convert a value from Double to CDouble without potentially changing it. This makes it difficult or impossible to use the FFI to call any functions with floating point parameters/return values.
Using a combination of unsafeCoerce (to shoehorn Double values in/out of CDoubles) and some functions written in C (to perform float<=>double), I was able to work around these problems, but that hardly seems like a nice solution.
-Infinity\r\nrealToFrac (-1/0 :: Float) :: Double\r\n --> -3.402823669209385e38\r\nrealToFrac (-0 :: Double) :: CDouble\r\n --> 0.0\r\n\r\nThe last item illustrates an important point: it is impossible to convert a value from Double to CDouble without potentially changing it. This makes it difficult or impossible to use the FFI to call any functions with floating point parameters/return values.\r\n\r\nUsing a combination of unsafeCoerce (to shoehorn Double values in/out of CDoubles) and some functions written in C (to perform float<=>double), I was able to work around these problems, but that hardly seems like a nice solution.","new":"As far as I can tell, the only way to convert between floating types in Haskell is to use realToFrac. Unfortunately, this function does some insane mangling of values when converting. Some examples:\r\n{{{\r\nrealToFrac (0/0 :: Double) :: Double\r\n --> -Infinity\r\nrealToFrac (-1/0 :: Float) :: Double\r\n --> -3.402823669209385e38\r\nrealToFrac (-0 :: Double) :: CDouble\r\n --> 0.0\r\n}}}\r\nThe last item illustrates an important point: it is impossible to convert a value from Double to CDouble without potentially changing it. This makes it difficult or impossible to use the FFI to call any functions with floating point parameters/return values.\r\n\r\nUsing a combination of unsafeCoerce (to shoehorn Double values in/out of CDoubles) and some functions written in C (to perform float<=>double), I was able to work around these problems, but that hardly seems like a nice solution."},"type_of_failure":{"old":null,"new":null},"blocking":{"old":null,"new":null}} -->
I just discovered that the issue is worse than originally described: the behaviour of realToFrac *changes* if it gets inlined. Consider the following:
module Main where{-# NOINLINE holycow #-}holycow :: (Real a, Fractional b) => a -> bholycow = realToFracmain = do print (realToFrac (0/0 :: Double) :: Double) print (holycow (0/0 :: Double) :: Double)
If you compile without optimisation, both lines print -Infinity as originally described. However, if you enable optimisation, the first line prints NaN (yay!) but the second line still prints -Infinity. GHC seems to be optimising based on the incorrect assumption that realToFrac :: Double -> Double is the identity function.
Yes, that's because the library GHC.Float contains the (clearly incorrect) RULE
"realToFrac/Double->Double" realToFrac = id :: Double -> Double
This ticket really really needs someone who knows and cares about numbers to figure out what the Right Thing is. It's a library issue involving only Haskell source code. Would someone like to help?
my gut feeling has been there should be a function or functions specifically to convert between different floating types. I think this is something that needs to be invented and proposed on libraries@ . (I'm not volunteering to do it this week because I'm going to a conference, but I'm willing to get the conversation started next week)
I think it's pretty straightforward. toRational should be able to convert NaN, Infinity and -Infinity correctly to Rational, and there is already code in fromRational to convert them back to the floating point types correctly (I imagine it's there in order to make things like read "NaN" work).
The Haskell report doesn't say anything about the implementation of toRational, so I think this is correct. I'm validating a patch now.
I've fixed this, but I'm not sure the fix is an improvement.
Thu Feb 11 02:19:55 PST 2010 Simon Marlow <marlowsd@gmail.com> * Handle NaN, -Infinity and Infinity in the toRational for Float/Double (#3676) M ./GHC/Float.lhs -2 +9 M ./GHC/Real.lhs -1 +2
and in ghc:
Thu Feb 11 05:15:43 PST 2010 Simon Marlow <marlowsd@gmail.com> * don't constant fold division that would result in negative zero (#3676)
It has some slightly odd consequences. Someone ought to take a broader look at this in the context of refining the Haskell standard. e.g.
The underlying problem is that the Rational type doesn't really include these strange values; it has been hacked in a few places so that certain unnormalised Rational values are used to represent strange floating point values, e.g. 0 :% 0 represents NaN and fromRational knows about it, but there's no official way to generate one of these (0/0 :: Rational gives an exception), they are only used "internally" by the libraries to make read "NaN" :: Double work.
The change I made is to make toRational (0/0 :: Double) generate 0 :% 0, and similarly for the other strange values, but these had odd consequences, because these special Rational values are not normalised. However, this does fix a real bug (the subject of this ticket).
While it presumably goes against the report, I personally feel that floating point types do not belong as members of the Real class *at all*. The same is true for a number of other classes that they belong to.
In the altfloat package, where I've been experimenting with solutions to this and other problems, I used a class
class FloatConvert a b where toFloating :: a -> b
which provides the necessary conversions: both between floating point types, and from real types to floating point types. It should be noted that the functions double2Float# and float2Double# in GHC.Exts perform correct conversion.
I suppose arithmetic on these non-normalised rationals does also not behave like on doubles (IEEE).
I would expect toRational to fail on NaN and Infinity and to convert -0 to 0.
But rather than introducing a multi-parameter type class I would just add non-overloaded plain conversion functions between Float, Double, CDouble, etc.
The problem with non-overloaded functions is that there are a lot of them: 12 with the four floating types that currently exist, and 30 if long double is ever added (two types: LDouble & CLDouble). This problem could be mitigated by only providing the trivial conversions when FFI types are involved: that is, provide Double <=> CDouble, but don't bother with Float <=> CDouble.
On the other hand, we could use a single parameter version of altfloat's technique as follows:
class FloatConvert a where toDouble :: a -> Double
since Double can represent all values of every other floating type (at least until long double exists). Then we could have a fromDouble member in the RealFloat class, and define toFloating = fromDouble . toDouble
Tue Feb 23 10:16:03 GMT 2010 Simon Marlow <marlowsd@gmail.com> * UNDO: Handle NaN, -Infinity and Infinity in the toRational for Float/Double (#3676)
in retrospect it wasn't a good idea to shoehorn these strange values into Rational.
The position now is that toRational (1/0 :: Double) is undefined. The Haskell standard does not define the result (it should), and GHC gives unpredictable results. Hence, realToFrac cannot convert accurately between floating-point types.
Someone should:
propose a proer API for conversion between floating-point types, e.g.
FloatConvert above, but make a full proposal.
decide whether toRational (1/0::Double) should be undefined or an
exception. If it is an exception, then we cannot optimise realToFrac
I agree that the "funny rationals" are a bit too strange a notion, especially when it comes to defining how arithmetic operations should behave.
After thinking about it, I'm fond of the toDouble/fromDouble approach as it seems consistent with the themes elsewhere in the library, except that it makes it harder to later add floating types that are wider than Double.
I can post this idea to the haskell-prime mailing list for discussion, if that's the right one.
There is no good solution to the problem while the conversions go (potentially, without rewrite rules) via Rational. Having 'illegal' Rational values like 1 :% 0 generated by parsing Infinity and so on is not a big problem because those are immediately passed to fromRational, so their treatment is confined to one place and they can be handled meaningfully there. But if they could be generated by toRational, every operation on Rationals would have to check for these values (and it's not always clear how to handle them).
The options I'm aware of are
non-overloaded conversion functions between each pair of types
a two-parameter conversion class
adding toMaxFloat and fromMaxFloat functions to RealFloat
adding a dedicated pseudo-numeric type for conversions
All of them have downsides. 1. and 2. would require a number of functions resp. instances quadratic in the number of involved types - yuck. 1. would even rule out polymorphic code. 3. has the problem of what MaxFloat should be and any choice would cause breakage when a larger RealFloat type is introduced. 4: A type with the sole purpose of facilitating numeric conversions in the standard libraries is quite wartish (and new types could introduce new unrepresentable values).
I think 4. is the least undesirable option, but still it would be a lot of work to implement (it involves a change to the language report, among other things) for dubious benefit.
Unless renewed concern is expressed, I think we should mark it as low priority.
Hrm, so we need a good conversion story / semantics for mapping between different floating point types? This is near and dear to some of my current work, I'll look into coming up with a good story, and if successful, propose it to the libraries list.
I think it would be a useful to at least note this problem in the docs for realToFrac:
-- | general coercion to fractional types---- Warning: avoid using this function to convert between floating-point-- number types. Consider using 'GHC.Float.float2Double' and-- 'GHC.Float.double2Float' instead. See-- <https://ghc.haskell.org/trac/ghc/ticket/3676 GHC bug #3676>.
(Incidentally, the documentation for GHC.Float is hidden from the users.)