[R6RS] NaNs and infinities
William D Clinger
will at ccs.neu.edu
Tue Mar 7 18:43:47 EST 2006
Thanks for looking at this tedious stuff so carefully, Kent.
Kent quoting my quotation of Mike:
> > > OK, but (nan? (sqrt -1.0)) => #t, right?
> I'm probably missing some context, but shouldn't (sqrt -1.0) be 0.0+1.0i?
Yes. I assumed Mike was talking about flsqrt, not sqrt,
and forgot to correct that when I quoted him.
> > +inf.0 represents the result of (/ 1.0 0.0).
> > -inf.0 represents the result of (/ -1.0 0.0).
> > +nan.0 represents the result of (/ 0.0 0.0).
> Are we going to require implementions to return values in these cases,
> or can they raise exceptions?
Good question. If we were to allow implementations to
raise an exception in all circumstances that would result
in an infinity or NaN with IEEE-754 arithmetic, and allow
them to raise an exception when they see any of the external
representations +inf.0, -inf.0, +nan.0, -nan.0, nan.0,
then we could write a much tighter spec that requires IEEE
behavior for infinities and NaNs. I like that.
> > (fl= +nan.0 fl) ==> unspecified
> > (fl= +nan.0 fl) ==> unspecified ; this example appears twice
> > (fl< +nan.0 fl) ==> unspecified
> I'd prefer these all return #f.
With our (admittedly broken) definition of +nan.0, that means
non-IEEE systems would probably *have* to raise an exception
when dividing by an inexact zero.
> > In safe mode, flfloor, flceiling, fltruncate, and flround
> > signal an error when passed an infinity or NaN. In unsafe
> > mode, all bets are off. In both safe and unsafe mode,
> > these procedures signal an error if no integral flonum
> > satisfying the above conditions exists. [Note: this is
> > not a good idea IMO, and is inconsistent with the examples
> > in any case.]
> I'd prefer that each behave as the identify when passed an infinity or
> NaN, even in safe mode. Those are the correct values in my understanding
> of the IEEE arithmetic model. I strongly dislike requiring them to
> raise an exception
> > The inexact-floor, inexact-ceiling, inexact-truncate, and
> > inexact-round procedures signal an error when passed an
> > infinity or NaN, or when no integral number satisfying
> > the above conditions exists.
> See comment for fl versions above.
Which comment for which fl versions?
> Would it be better to allow implementations that don't support IEEE
> arithmetic to leave NaNs and infinities out, and create a more precise
> specification for those that do support IEEE arithmetic?
Probably. One of the reasons we're going through this exercise
is to answer that question, i.e. to see whether the SRFI 77 model
makes sense for non-IEEE systems.
> > (integer? -inf.0) ==> #f
> If (integer? 0.0) => #t, I'd prefer that (integer? -inf.0) be #t.
If (integer? -inf.0) is #t, then -inf.0 would be an integer
that isn't rational, which seems strange. We just went to
some trouble to eliminate a similar strangeness in SRFI 77,
and I'm not eager to create that problem again.
> > (fldenominator +inf.0) ==> 1.0 ; should be unspecified
> > (fldenominator -inf.0) ==> 1.0 ; should be unspecified
> I prefer 1.0 but don't really care. Should we say at least that the
> denominator in the first case is positive, not +nan.0, and not +inf.0
> or simply that (/ (flnumerator +inf.0) (fldenominator +inf.0)) ==> +inf.0,
> with something similar for the second case?
I like both ideas, especially the second.
> > (flfloor +inf.0) ==> +inf.0 ; should be unspecified or "is an error"
> I prefer +inf.0. I really dislike "is an error".
Me too. I wouldn't object to "may raise a &domain exception",
but I would object to "should raise".
> > (flceiling -inf.0) ==> -inf.0 ; should be unspecified or "is an error"
> I prefer -inf.0. I really dislike "is an error".
> > (flexp +inf.0) ==> +inf.0
> > (flexp -inf.0) ==> 0.0
> > (fllog +inf.0) ==> +inf.0 ; should be unspecified
> I prefer +inf.0. We should at least require (exp (fllog +inf.0)) ==> +inf.0.
I agree with that requirement.
> I'd like to know the rationale for these unspecified items. Some of them
> are apparently to accommodate non-IEEE arithmetic. Are there other
> rationales at play?
There were about four unspecified examples involving arithmetic
that mixes exact with inexact arguments. I'll address those
below. I think the other unspecified values were all to allow
> > (flsqrt +inf.0) ==> +inf.0 ; should be unspecified
> I prefer +inf.0. We should at least require that
> (let ([x (flsqrt +inf.0)]) (* x x)) ==> +inf.0.
I agree with that requirement.
Mixed mode arithmetic.
> > (/ 0 0.0) ==> unspecified
> I would prefer (/ 0 0.0) return 0. In my understanding of the IEEE world,
> 0.0 is something that is just slightly on the positive side of 0, so
> (/ 0 0.0) is 0. Is the intent is to accommodate non-IEEE-arithmetic,
> or is there some differing opinion on what this should return in a system
> that supports IEEE arithmetic?
If 0.0 represents an ever-so-slightly positive real,
then why does IEEE-754 require (/ 0.0 0.0) to evaluate
to a NaN instead of 1.0?
My understanding of IEEE-754 is different from yours.
In my understanding, 0.0 and -0.0 are justified on an
ad hoc basis, by their disambiguation of several cases
that would otherwise result in a NaN instead of +inf.0
or -inf.0, and by their utility in defining branch cuts.
If we are going to use IEEE-754 as a guide to the behavior
of mixed mode arithmetic, then perhaps we should look at
how mixed mode arithmetic actually behaves in current
systems, including other languages, that use IEEE-754
arithmetic. Here is a small chart:
MzScheme Chez Larceny Gambit Bigloo MIT-Scheme Java Allegro
v301 v6.1 v0.90 v3.0 v2.0 v7.3.1 5.0 5.0.1
(* 0 3.5) 0 0 0.0 0.0 0.0 0 0.0 0.0
(* 3.5 0) 0 0 0.0 0.0 0.0 0 0.0 0.0
(* 0 +inf.0) 0 0 +nan.0 +nan.0 +nan.0 0 +nan.0 +nan.0
(* +inf.0 0) 0 0 +nan.0 +nan.0 +nan.0 0 +nan.0 +nan.0
(* 0 +nan.0) 0 0 +nan.0 +nan.0 +nan.0 0 +nan.0 +nan.0
(* +nan.0 0) 0 0 +nan.0 +nan.0 +nan.0 0 +nan.0 +nan.0
(/ 1 0.0) +inf.0 +inf.0 +inf.0 +inf.0 +inf.0 +inf.0 +inf.0 error
(/ 0 0.0) 0 0 +nan.0 +nan.0 +nan.0 0 +nan.0 error
(/ 0.0 0.0) +nan.0 +nan.0 +nan.0 +nan.0 +nan.0 +nan.0 +nan.0 error
(/ 0.0 0) error error +nan.0 error +nan.0 +nan.0 +nan.0 error
(/ 1.0 0) error error +inf.0 error +inf.0 +inf.0 +inf.0 error
(sqrt 0) 0 0 0.0 0 0.0 0 0.0 0.0
(sqrt 3) 1.73# 1.73# 1.73# 1.73# 1.73# 1.73# 1.73# 1.73#
(sqrt 4) 2 2 2.0 2 0.0 2 2.0 2.0
All of these behaviors, even the Java and Common Lisp
behaviors, are allowed by the R5RS. No one behavior has
a consensus. Indeed, (sqrt 3) is the only expression
that returns the same value in all these systems, but
it would not be hard to find systems in which (sqrt 3)
returns a slightly different value. (Indeed, some of
the values returned by sqrt are likely to be different
under Windows than under Solaris, due to the Pentium's
double rounding problem.)
The behaviors exhibited by Larceny, Gambit, Bigloo, Java,
and Common Lisp preserve the closure properties that were
part of the rationale for SRFI 77. The behaviors exhibited
by MzScheme, Chez Scheme, and MIT Scheme do not have those
closure properties, but they are very much in tune with the
model for arithmetic that was adopted in the RRRS that came
out of the Brandeis meeting.
There's something to be said for picking one of these
behaviors and requiring all systems to implement it, but
that would not be an easy decision to make. If we were
to require some particular behavior, I would argue for
a behavior that has the closure properties described in
Some of the participants in the SRFI 77 discussion would
argue as I would. IIRC, the participants who argued for
allowing (* 0 3.5) to evaluate to an exact 0 were mainly
concerned that this behavior be allowed; I don't remember
anyone arguing that it should be required (although there
were some pretty strange things said in that discussion).
More information about the R6RS