-
Notifications
You must be signed in to change notification settings - Fork 32
R7RSstrictArithmetic
In Larceny v1.3, the integer?
, rational?
, and real?
procedures exported by (scheme base)
have R6RS semantics. It is not clear whether that is fully compatible with the R7RS (small) standard, because the R7RS specification of those procedures contradicts itself by saying "(real? z)
is true if and only if (zero? (imag-part z))
is true" while giving the following two examples drawn from the R6RS standard, where they serve to emphasize that "(real? z)
is true if and only if (zero? (imag-part z))
and (exact? (imag-part z))
are both true."
(real? -2.5+0i) ==> #t
(real? -2.5+0.0i) ==> #f
That has implications for integer?
and rational?
because integers must be rational and rationals must be real.
The R7RS contains a "not authoritative" and "possibly incomplete" list of incompatibilities with R6RS that says the R6RS procedures real-valued?
, rational-valued?
, and integer-valued?
were not adopted. That list of incompatibilities says nothing about any changes to the semantics of real?
, rational?
, or integer?
. The fact that three related procedures were mentioned but real?
, rational?
, and integer?
were not can be interpreted as evidence suggesting the omission of real?
, rational?
, and integer?
from the list of incompatibilities was less likely to be an oversight than the omission of "and (exact? (imag-part z))
" from the R7RS specification of the real?
procedure.
On the other hand, it is possible to argue that both the examples and the list of incompatibilities are "not authoritative" and that the omission of "and (exact? (imag-part z))
" should be taken as deliberate. Several members of WG1 have said they do not recall any discussion of this, and there was certainly no discussion when the R7RS was ratified, but at least one R7RS editor believes WG1 deliberately broke backward compatibility and interoperability with R6RS by changing the semantics of the real?
procedure.
No one has yet come up with a real-world example of portable R7RS code that fails to work with the R6RS semantics. At least three of Larceny's compiler tables use the R6RS semantics to improve the performance of flonum and compnum arithmetic, so there is a real-world argument for the R6RS semantics and no real-world argument against.
There is, however, a pedantic language-lawyer argument that says the R7RS was intended to be incompatible with the R6RS on this matter. At least one R7RS editor has been making that argument.
Is there any way to remove this alleged incompatibility by arranging for -r7strict
mode to be compatible with both the R6RS semantics of real?
and the seemingly incompatible pedantic interpretation of the R7RS?
One approach is to give up on backward compatibility and interoperability by having -r7strict
mode use a different version of (scheme base)
in which real?
has the pedantic semantics instead of the R6RS semantics. That would utterly destroy interoperability because Larceny's library manager does not allow more than one version of a library to be loaded. It would not be possible to use a library that imports the pedantic version of (scheme base)
in the same program with a library that imports the backward-compatible version of (scheme base)
.
Maintaining a completely separate set of compiled libraries for -r7
and -r7strict
would create many new opportunities for confusion and bugs, so we need to find a solution in which those two modes use the same version of (scheme base)
.
We'd like to find a solution in which all differences between -r7
and -r7strict
are controlled by a run-time switch, as in Larceny 1.3. That would allow both modes to use the same compiled libraries.
The behavior of real?
could be made sensitive to the mode using a run-time switch. That would be a practical solution for interpreters, but is not feasible for optimizing compilers that rely on properties of real?
at compile time.
To implement the pedantic interpretation of R7RS without breaking backward compatibility and interoperability, we need a solution in which (zero? (imag-part (make-rectangular x1 0.0)))
is false.
The R7RS specification of make-rectangular
implies (imag-part (make-rectangular x1 0.0))
is 0.0, so we need a solution in which (zero? 0.0)
is false.
That appears to be compatible with the R7RS. The R7RS does not require (= 0 0.0)
to be true any more than it requires (= #e1e23 1e23)
to be true.
The R7RS does require (= +0.0 -0.0)
to be true. The R7RS does not require (eqv? +0.0 -0.0)
to be true.
The best solution we have been able to come up with achieves binary compatibility between the modes by using IEEE arithmetic in the usual R7RS mode but a variation of IEEE arithmetic in -r7strict
mode. In -r7strict
mode, the binary representations of +0.0 and -0.0 are re-interpreted as a very small but positive inexact number. This can be done by examining the run-time mode switch only in mixed-mode arithmetic comparisons, conversions from inexact to exact, conversions between inexact numbers and strings, and in the ceiling
and integer?
procedures:
-
(exact +0.0)
and(exact -0.0)
return #e1e-999 - IEEE arithmetic operations deliver results consistent with that re-interpretation
- IEEE floating-point comparisons deliver results consistent with that re-interpretation, except
-
(zero? +0.0)
and(zero? -0.0)
will need to return false -
(positive? +0.0)
and(positive? -0.0)
will need to return true -
(integer? +0.0)
and(integer? -0.0)
will need to return false -
(ceiling +0.0)
and(ceiling -0.0)
will need to return 1.0
-
- mixed-mode arithmetic comparisons can be made consistent with that re-interpretation using a run-time mode switch in the mixed-mode contagion routines
-
(number->string +0.0)
returns"1e-999"
-
(number->string -0.0)
returns"1e-0999"
-
(string->number "1e-0999")
returns the equivalent of -0.0 (for write/read invariance)
The last three aren't necessary for conformance, but it would be confusing for 0.0 to print as 0.0 when (zero? 0.0)
is false.
The R7RS requires the following procedures to accept inexact as well as exact integer arguments, but also allows them to be extended to accept non-integer arguments:
odd?
even?
floor/
floor-quotient
floor-remainder
truncate/
truncate-quotient
truncate-remainder
quotient
remainder
modulo
gcd
lcm
Another possibility is to regard +0.0 and -0.0 as very small irrational or even infinitesimal numbers. We have a lot of freedom here, because the R7RS specification of exact
says it "may return a rational approximation, or may report an implementation violation" when given an inexact real that is not an integer.
SRFI 144 adds constants and procedures for flonum arithmetic. Its text says
When a C99 variable, procedure, macro, or operator is specified for a procedure in this SRFI, the semantics of the Scheme variable or procedure are the same as its C equivalent. The definitions given here of such procedures are informative only; for precise definitions, users and implementers must consult the Posix or C99 standards. This applies particularly to the behavior of these procedures on -0.0, +inf.0, -inf.0, and +nan.0. However, conformance to this SRFI does not require that these numbers exist or are flonums.
The double precision C equivalents of SRFI 144 procedures take IEEE-754 double precision arguments and deliver IEEE-754 double precision numbers as results. That means the 1e-999 value used to approximate 0.0 in Larceny's -r7strict
mode must be converted to IEEE double precision zero when passed as an argument, and must be obtained as a result only by converting from IEEE double precision zero. Although Larceny implements some SRFI 144 procedures without calling C functions, it seems best to implement those procedures as though they called C functions. A similar argument holds for the constants. That has the following implications for -r7strict
mode:
- fl-least should be Larceny's best approximation to the least positive IEEE-754 double precision number, rather than 1e-999
-
(flzero? 0.0)
should return true rather than false -
(flpositive? 0.0)
should return false rather than true -
(flinteger? 0.0)
should return true rather than false -
(flceiling 0.0)
should return Larceny's best approximation to 0.0 rather than 1.0 -
(flnumerator 0.0)
should return 0.0 rather than 1.0 -
(fldenominator 0.0)
should return 1.0 rather than +inf.0
That will undoubtedly cause some confusion, but there seems to be no way to avoid confusion when implementing a semantics that satisfies both the backward-compatible R6RS semantics and the pedantic interpretation of R7RS semantics.