Gauche supports IEEE754 negative zero -0.0. It simply wraps an IEEE754 double as a scheme object, so mostly it just works as specified in IEEE754 (and as supported by the underlying math library). Or, so we thought.
Let's recap the behavior of -0.0. It's numerically indistinguishable from 0.0 (so, it is not "an infinitely small value less than zero"):
(= -0.0 0.0) ⇒ #t (< -0.0 0.0) ⇒ #f (zero? -0.0) ⇒ #t
But it can make a difference when there's a functon f(x) such that it is discontinuous at x = 0, and f(x) goes to different values when x approaches to zero from positive side or negative side.
(/ 0.0) ⇒ +inf.0 (/ -0.0) ⇒ -inf.0
For arithmetic primitive procedures, we simply pass unboxed double to the underlying math functions, so we didn't think we need to handle -0.0 specially.
The first wakeup call was this article via HackerNews:
It talks about writing
abs in Java, but every time I saw articles like
this I just try it out on Gauche, and alas!
;; Gauche 0.9.10 (abs -0.0) ⇒ -0.0 ; Ouch!
Yeah, the culprit was the C implementation of
abs, the gist of which was:
if (x < 0.0) return -x; else return x;
-0.0 doesn't satisfy
x < 0.0 so it was returned without negation.
The easy fix is to use
if (signbit(x)) return -x;
I reported the fix on Twitter, then somebody raised an issue: What about
(eqv? -0.0 0.0)?
My initial reaction was that it should be
(= -0.0 0.0)
#t. In fact, R5RS states this:
#tif: ... obj1 and obj2 are both numbers, are numerically equal (see
=...), and are either both exact or both inexact.
However, I realized that R7RS has more subtle definition.
#fif: ... obj1 and obj2 are both inexact numbers such that either they are numerically unequal (in the sense of
=), or they do not yield the same results (...) when passed as arguments to any other procedure that can be defined as a finite composition of Scheme's standard arithmetic procedures, ...
Clearly, -0.0 and 0.0 don't yield the same results when passed to
so it should return
#f. (It is also mentioned in 6.2.4
that -0.0 is distinct from 0.0 in a sense of
Fix for this is a bit involved. When I fixed
eqv?, a bunch of
tests started failing. It looks like some
inexact integer division routines in the tests yield -0.0, and
are compared to 0.0 with
equal?, which should follow
if arguments are numbers.
It turned out that the root cause was rounding primitives returning -0.0:
;; Gauche 0.9.10 (ceiling -0.5) ⇒ -0.0
Although this itself is plausible, in most of the cases when you're thinking of integers (exact or inexact), you want to treat zero as zero. Certainly you don't want to deal with two distint zeros in quotients or remainders. The choices would be either leave the rounding primitives as are and fix the integer divisions, or change the rounding primitives altogether. I choose the latter.
The fixes are in the HEAD now.
;; Gauche 0.9.11 (eqv? -0.0 0.0) ⇒ #f (ceiling -0.5) ⇒ 0.0