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