GHC: Ticket #3676: realToFrac doesn't sanely convert between floating types
http://ghc.haskell.org/trac/ghc/ticket/3676
<p>
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:
</p>
<pre class="wiki">realToFrac (0/0 :: Double) :: Double
--> -Infinity
realToFrac (-1/0 :: Float) :: Double
--> -3.402823669209385e38
realToFrac (-0 :: Double) :: CDouble
--> 0.0
</pre><p>
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.
</p>
<p>
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.
</p>
en-usGHChttp://ghc.haskell.org/trac/ghc/chrome/site/ghc_logo.png
http://ghc.haskell.org/trac/ghc/ticket/3676
Trac 1.2iglooFri, 20 Nov 2009 22:01:42 GMTdescription changed
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:1
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:1
<ul>
<li><strong>description</strong>
modified (<a href="/trac/ghc/ticket/3676?action=diff&version=1">diff</a>)
</li>
</ul>
TicketiglooFri, 20 Nov 2009 22:09:57 GMTmilestone set
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:2
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:2
<ul>
<li><strong>milestone</strong>
set to <em>6.12.2</em>
</li>
</ul>
<p>
Thanks for the report.
</p>
TicketiglooSun, 22 Nov 2009 00:00:24 GMT
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:3
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:3
<p>
See also <a class="new ticket" href="http://ghc.haskell.org/trac/ghc/ticket/3070" title="#3070: bug: floor(0/0) should not be defined (new)">#3070</a>
</p>
TicketdraconxTue, 09 Feb 2010 17:24:54 GMTversion changed
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:4
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:4
<ul>
<li><strong>version</strong>
changed from <em>6.10.4</em> to <em>6.12.1</em>
</li>
</ul>
<p>
Still present in 6.12.1.
</p>
<p>
I just discovered that the issue is worse than originally described: the behaviour of realToFrac *changes* if it gets inlined. Consider the following:
</p>
<pre class="wiki">module Main where
{-# NOINLINE holycow #-}
holycow :: (Real a, Fractional b) => a -> b
holycow = realToFrac
main = do
print (realToFrac (0/0 :: Double) :: Double)
print (holycow (0/0 :: Double) :: Double)
</pre><p>
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.
</p>
TicketsimonpjWed, 10 Feb 2010 11:51:50 GMT
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:5
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:5
<p>
Yes, that's because the library <code>GHC.Float</code> contains the (clearly incorrect) RULE
</p>
<pre class="wiki">"realToFrac/Double->Double" realToFrac = id :: Double -> Double
</pre><p>
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?
</p>
<p>
Thanks
</p>
<p>
Simon
</p>
TicketisaacdupreeWed, 10 Feb 2010 18:32:24 GMT
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:6
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:6
<p>
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)
</p>
<p>
-Isaac Dupree
</p>
TicketsimonmarThu, 11 Feb 2010 10:02:38 GMTstatus changed; owner set
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:7
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:7
<ul>
<li><strong>owner</strong>
set to <em>simonmar</em>
</li>
<li><strong>status</strong>
changed from <em>new</em> to <em>assigned</em>
</li>
</ul>
TicketsimonmarThu, 11 Feb 2010 10:12:02 GMT
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:8
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:8
<p>
I think it's pretty straightforward. <code>toRational</code> 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 <code>read "NaN"</code> work).
</p>
<p>
The Haskell report doesn't say anything about the implementation of <code>toRational</code>, so I think this is correct. I'm validating a patch now.
</p>
TicketdraconxThu, 11 Feb 2010 13:27:20 GMT
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:9
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:9
<p>
Don't forget negative zero.
</p>
TicketsimonmarThu, 11 Feb 2010 14:10:08 GMT
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:10
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:10
<p>
I've fixed this, but I'm not sure the fix is an improvement.
</p>
<pre class="wiki">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
</pre><p>
and in ghc:
</p>
<pre class="wiki">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)
</pre><p>
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.
</p>
<pre class="wiki">Prelude> toRational (-0 :: Double) == - toRational (0 :: Double)
False
</pre><p>
whereas previously this was True.
</p>
<p>
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. <code>0 :% 0</code> represents <code>NaN</code> and <code>fromRational</code> knows about it, but there's no official way to generate one of these (<code>0/0 :: Rational</code> gives an exception), they are only used "internally" by the libraries to make <code>read "NaN" :: Double</code> work.
</p>
<p>
The change I made is to make <code>toRational (0/0 :: Double)</code> generate <code>0 :% 0</code>, and similarly for the other strange values, but these had odd consequences, because these special <code>Rational</code> values are not normalised. However, this does fix a real bug (the subject of this ticket).
</p>
<p>
What do people think?
</p>
TicketdraconxThu, 11 Feb 2010 14:40:07 GMT
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:11
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:11
<p>
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.
</p>
<p>
In the altfloat package, where I've been experimenting with solutions to this and other problems, I used a class
</p>
<pre class="wiki">class FloatConvert a b where
toFloating :: a -> b
</pre><p>
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 <code>double2Float#</code> and <code>float2Double#</code> in GHC.Exts perform correct conversion.
</p>
TicketmaederThu, 11 Feb 2010 16:42:52 GMT
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:12
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:12
<p>
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.
</p>
<p>
But rather than introducing a multi-parameter type class I would just add non-overloaded plain conversion functions between Float, Double, CDouble, etc.
</p>
TicketdraconxThu, 11 Feb 2010 18:22:50 GMT
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:13
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:13
<p>
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.
</p>
<p>
On the other hand, we could use a single parameter version of altfloat's technique as follows:
</p>
<pre class="wiki">class FloatConvert a where
toDouble :: a -> Double
</pre><p>
since Double can represent all values of every other floating type (at least until long double exists). Then we could have a <code>fromDouble</code> member in the RealFloat class, and define <code>toFloating = fromDouble . toDouble</code>
</p>
TicketsimonmarTue, 23 Feb 2010 11:08:25 GMTmilestone changed
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:14
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:14
<ul>
<li><strong>milestone</strong>
changed from <em>6.12.2</em> to <em>_|_</em>
</li>
</ul>
<p>
I rolled back the patch to libraries/base:
</p>
<pre class="wiki">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)
</pre><p>
in retrospect it wasn't a good idea to shoehorn these strange values into Rational.
</p>
<p>
The position now is that <code>toRational (1/0 :: Double)</code> is undefined. The Haskell standard does not define the result (it should), and GHC gives unpredictable results. Hence, <code>realToFrac</code> cannot convert accurately between floating-point types.
</p>
<p>
Someone should:
</p>
<ul><li>propose a proer API for conversion between floating-point types, e.g.
<code>FloatConvert</code> above, but make a full proposal.
</li></ul><ul><li>decide whether <code>toRational (1/0::Double)</code> should be undefined or an
exception. If it is an exception, then we cannot optimise <code>realToFrac</code>
to a direct conversion, e.g. <code>floatToDouble#</code>.
</li></ul>
TicketdraconxTue, 23 Feb 2010 14:12:20 GMT
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:15
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:15
<p>
I agree that the "funny rationals" are a bit too strange a notion, especially when it comes to defining how arithmetic operations should behave.
</p>
<p>
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.
</p>
<p>
I can post this idea to the haskell-prime mailing list for discussion, if that's the right one.
</p>
TicketdraconxThu, 04 Mar 2010 18:23:36 GMT
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:16
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:16
<p>
OK, I've posted this to the haskell-prime mailing list.
</p>
<p>
<a class="ext-link" href="http://thread.gmane.org/gmane.comp.lang.haskell.prime/3146"><span class="icon"></span>http://thread.gmane.org/gmane.comp.lang.haskell.prime/3146</a>
</p>
TicketsimonmarWed, 11 Aug 2010 13:25:24 GMTowner deleted
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:17
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:17
<ul>
<li><strong>owner</strong>
<em>simonmar</em> deleted
</li>
</ul>
Ticketdaniel.is.fischerFri, 29 Jul 2011 23:39:37 GMTcc set
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:18
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:18
<ul>
<li><strong>cc</strong>
<em>daniel.is.fischer@…</em> added
</li>
</ul>
<p>
Summarising:
</p>
<p>
There is no good solution to the problem while the conversions go (potentially, without rewrite rules) via <code>Rational</code>. Having 'illegal' <code>Rational</code> values like <code>1 :% 0</code> generated by parsing <code>Infinity</code> and so on is not a big problem because those are immediately passed to <code>fromRational</code>, so their treatment is confined to one place and they can be handled meaningfully there. But if they could be generated by <code>toRational</code>, <em>every</em> operation on <code>Rational</code>s would have to check for these values (and it's not always clear how to handle them).
</p>
<p>
The options I'm aware of are
</p>
<ol><li>non-overloaded conversion functions between each pair of types
</li><li>a two-parameter conversion class
</li><li>adding <code>toMaxFloat</code> and <code>fromMaxFloat</code> functions to <code>RealFloat</code>
</li><li>adding a dedicated pseudo-numeric type for conversions
</li></ol><p>
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 <code>MaxFloat</code> should be and any choice would cause breakage when a larger <code>RealFloat</code> 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).
</p>
<p>
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.
</p>
<p>
Unless renewed concern is expressed, I think we should mark it as low priority.
</p>
TicketcarterSat, 20 Jul 2013 05:57:54 GMTdifficulty set
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:19
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:19
<ul>
<li><strong>difficulty</strong>
set to <em>Unknown</em>
</li>
</ul>
<p>
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.
</p>
TicketthoughtpoliceTue, 07 Oct 2014 05:47:37 GMTcomponent changed; owner set
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:20
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:20
<ul>
<li><strong>owner</strong>
set to <em>ekmett</em>
</li>
<li><strong>component</strong>
changed from <em>libraries (other)</em> to <em>Core Libraries</em>
</li>
</ul>
<p>
Moving over to new owning component 'Core Libraries'.
</p>
TicketlelfFri, 09 Jan 2015 12:22:41 GMTcc changed
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:21
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:21
<ul>
<li><strong>cc</strong>
<em>me@…</em> <em>core-libraries-committee@…</em> added
</li>
</ul>
TicketekmettSun, 22 Feb 2015 12:10:51 GMTkeywords set
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:22
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:22
<ul>
<li><strong>keywords</strong>
<em>report-impact</em> added
</li>
</ul>
<p>
Any action we take on this would have an effect on the Haskell Report. Noting this accordingly.
</p>
TicketthomieThu, 17 Sep 2015 09:19:49 GMTowner deleted
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:23
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:23
<ul>
<li><strong>owner</strong>
<em>ekmett</em> deleted
</li>
</ul>
TicketRufflewindThu, 14 Apr 2016 01:37:19 GMTcc changed
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:24
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:24
<ul>
<li><strong>cc</strong>
<em>ekmett</em> added
</li>
</ul>
<p>
I think it would be a useful to at least note this problem in the docs for <code>realToFrac</code>:
</p>
<pre class="wiki">-- | 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>.
</pre><p>
(Incidentally, the documentation for <code>GHC.Float</code> is hidden from the users.)
</p>
TicketekmettThu, 14 Apr 2016 19:01:44 GMT
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:25
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:25
<p>
I definitely agree that documenting the issues around <code>realToFrac</code> would be a good idea.
</p>
TicketsimonmarThu, 14 Apr 2016 22:56:56 GMT
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:26
http://ghc.haskell.org/trac/ghc/ticket/3676#comment:26
<p>
Documenting things is good, but
</p>
<ul><li>Instead of linking to the ticket, explain *why* the function shouldn't be used
</li><li>We cannot recommend using internal APIs (<code>GHC.Float</code>)
</li></ul>
Ticket