[Haskell-cafe] How odd...

Paul Johnson paul at cogito.org.uk
Sat Aug 4 13:07:47 EDT 2007


Andrew Coppin wrote:
> Paul Johnson wrote:
>> > log 0
>> -Infinity
> Oh. So... since when does Haskell know about infinity?
I should have mentioned that the underlying platform in my case is an
Intel P4.  Haskell does not specify a floating point implementation; the
assumption is that it uses whatever the platform provides because
anything else would be horribly inefficient.  The P4 implements IEEE
floating point, which as Andrew pointed out includes Infinity, -Infinity
and NaN as special cases of floating point values.  It also seems to
distinguish between 0.0 and (-0.0), although they are still equal.  For
instance:

> (-0.0)
-0.0

> 1.0 / 0.0
Infinity

> 1.0 / (-0.0)
-Infinity

(Aside: should Infinity etc. be included in the Haskell standard?  Or
possibly in a Data.Numeric.IEEE extension?  They look like constructors,
and it would be nice to be able to pattern match on them.)

So if you tried these experiments on a non-IEEE platform then you would
get different results.  You might get exceptions or just wierd big
numbers.  ISTR someone posted results from a Sun Sparc along these lines
some time ago.
>  foo x
>    | x < 0 = ...
>    | x == 0 = ...
>    | x > 0 = ... 
> I was most perplexed when I got a "non-exhaustive patterns" 
> exception... It turns out there was a NaN in there. I forget about that.
Nasty.  I'll have to bear that one in mind myself.

You can detect infinities by equality, but not NaN.

> let inf = (1.0 / 0.0)
> let nan = inf * 0.0
> inf == inf
True
>nan == nan
False

>> > exp (log 0 * 2)
>> 0.0
> Well that's interesting. I did wonder why it *doesn't* break in the 
> real case...
I haven't perused the IEEE standard, but I expect it defines something
like this:

Infinity * 0 = NaN
Infinity * _ = Infinity
exp Infinity = Infinity
exp (-Infinity) = 0
> Um... why would infinity * 0 be NaN? That doesn't make sense...
Infinity times anything is Infinity.  Zero times anything is zero.  So
what should Infinity * zero be?  There isn't one right answer.  In this
case the "morally correct" answer is zero, but in other contexts it
might be Infinity or even some finite number other than zero.

Consider 0.0 / 0.0, which also evaluates to NaN.  What should its value
be?  If you take the limit of (x / x) as x -> 0 then the right answer is
0.  On the other hand if you take the limit of (0 / x) as x -> 0 then
the right answer is infinity.  Worse yet, if you take the limit of (2x /
x) as x-> 0 then the right answer is 2.  You can pick any finite or
infinite value you like.  So the only possible answer is NaN.
>
>> So no, its not a bug, its according to the standard.
>
> So I'm the only person who was expecting zero squared to be zero? 
> (IMHO the standard should try to implement mathematical operations in 
> a mathematically sensible way...)
It does *try*.  I'm not sure if IEEE arithmetic was actually defined
back in 98.  It certainly wasn't widely implemented.  There might well
be a case for revisiting the standard to allow for IEEE values, but you
can't mandate them because not all platforms support IEEE arithmetic.
>> While working through this I also came across the following case 
>> which technically is a bug:
>>
>> > 0 ** 0
>> 1.0
>>
>> > exp (log 0 * 0)
>> NaN
>>
>> I suspect that GHCi is using a built-in exponentiation operator that 
>> doesn't quite conform to the standard in this case.
>
> Now that really *is* odd...
When I said "built in" I meant "built in to the hardware".  This is
probably another special case defined in the IEEE standard, which is not
the same as the Haskell 98 definition.

The reason why the IEEE standard was defined in the first place was that
floating point software portability was being seriously limited by these
corner cases.  You would get some numerical code working on a Sun, and
then find it broke on a PC because one of them defined (0.0 / 0.0) as 1
and the other defined it as 0.   Worse yet, you might find it worked on
Intel chips but not IBM or AMD.  Programmers also had to code explicit
checks for division by zero and implement their own versions of Infinity
and NaN in cases where they might appear, which cluttered up the code
and slowed down execution.

One way out of this morass would be to define Haskell floating point
arithmetic as IEEE standard, and document non-conformance for certain
platforms.  In the long term that is probably the way forwards (do any
currently sold chips *not* implement IEEE?).  It would also let us
include Infinity and NaN as constructors.  However it would lead to
significant problems when compiling code that used these values on
non-IEEE platforms.  What do you do then?

Paul.



More information about the Haskell-Cafe mailing list