Title

Preliminary Proposal for R6RS Arithmetic

Authors

William D Clinger and Michael Sperber

Status

This SRFI is currently in ``draft'' status. For an explanation of each status that a SRFI can hold, see here. You can access the discussion via the archive of the mailing list.

This SRFI is being submitted by members of the Scheme Language Editor's Committee as part of the R6RS Scheme standardization process. The purpose of such ``R6RS SRFIs'' is to inform the Scheme community of features and design ideas under consideration by the editors and to allow the community to give the editors some direct feedback that will be considered during the design process.

At the end of the discussion period, this SRFI will be withdrawn. When the R6RS specification is finalized, the SRFI may be revised to conform to the R6RS specification and then resubmitted with the intent to finalize it. This procedure aims to avoid the situation where this SRFI is inconsistent with R6RS. An inconsistency between R6RS and this SRFI could confuse some users. Moreover it could pose implementation problems for R6RS compliant Scheme systems that aim to support this SRFI. Note that departures from the SRFI specification by the Scheme Language Editor's Committee may occur due to other design constraints, such as design consistency with other features that are not under discussion as SRFIs.

Table of contents

Abstract

Scheme's arithmetic system was designed to allow a wide variety of implementations. After many years of implementation experience, however, most implementations now fall into a small number of categories, and the benefits of continued experimentation no longer justify the confusion and portability problems that have resulted from giving implementations so much freedom in this area. Moreover, the R5RS generic arithmetic is difficult to implement as efficiently as purely fixnum or purely flonum arithmetic. (Fixnum arithmetic is typically limited-precision integer arithmetic implemented using the underlying machine's representation; flonum arithmetic is typically limited-precision floating-point arithmetic using the underlying machine's representation.)

This SRFI is an effort to extend and clarify the R5RS arithmetic to make it more portable, more comprehensive, and enable faster programs.

Furthermore, one of us (Sperber) has argued that Scheme's arithmetic requires radical overhaul. The other (Clinger) agrees that revisions are needed. This SRFI describes revisions that would satisfy both of us and states the choice between both of them as an issue to be resolved in the discussion period of this SRFI. Whether these revisions qualify as radical is best left to the judgement of individual readers.

This SRFI proposes to revise section 6.2 ("Numbers") of R5RS by:

Issues

Members of the Scheme community are encouraged to express themselves on the following issues:

Rationale

Most implementations of Scheme fall into one of the following categories:

Under R5RS, it is hard to write programs whose arithmetic is portable across the above categories, and it is unnecessarily difficult even to write programs whose arithmetic is portable between different implementations in the same category.

The portability problems can most easily be solved by requiring all implementations to support the full numeric tower. To prevent that requirement from making Scheme substantially more difficult to implement, we provide a reference implementation that constructs the full numeric tower from a fixnum/flonum base. To ensure the portability of such reference implementations, the fixnum/flonum base must be described and (at least partially) standardized.

Fixnum/flonum arithmetic is already supported by many systems, mainly for efficiency. Standardization of fixnum/flonum arithmetic would increase the portability of code that uses it, but we cannot standardize the precision of fixnum/flonum arithmetic without making it inefficient on some systems, which would defeat its purpose. We therefore propose to specify the syntax and much of the semantics of fixnum/flonum arithmetic, but to make the precision a parameter of the specification.

Several details of R5RS are inconsistent or incomplete with respect to the IEEE standards for binary floating point arithmetic, which are generally used to implement Scheme's inexact real arithmetic. Furthermore, some details of R5RS make it unnecessarily difficult to implement Scheme's arithmetic efficiently.

This SRFI is incompatible with SRFI 70 [Jaffer 2005] in several ways, including:

Both of the above differences are motivated by closure properties that make it easier for an implementation to generate efficient numerical code.

Moreover, unlike SRFI 70, this SRFI doesn't extend quotient, remainder, and modulo to real numbers, but instead provides additional operations div and mod, which are slightly more generally applicable and easier to specify. It also does not extend the gcd and lcm to rational numbers.

The sections on Fixnums, Flonums, Exact Arithmetic, and Inexact Arithmetic are new in the SRFI and not in SRFI 70. Moreover, the section Generic Exact Arithmetic is also new here. This SRFI also omits SRFI 70's discussion of infinities and offers its own.

Other, more minor differences with SRFI 70 are highlighted in the hypertext. (The sections mentioned in the previous paragraph are not marked up, as they would be (almost in the case of generic exact arithmetic) completely red.

This SRFI describes several alternative approaches to completely generic arithmetic. The alternative described in the section R5RS-style Generic Arithmetic follows the essence of R5RS, with the advantage of historical continuity and contagiousness of inexact operations. The alternative described in section Generic Exact Arithmetic has exactness be contagious, with the advantage of greater transparency and the validity of standard algebraic laws over the R5RS-style generic arithmetic. The section describing generic exact arithmetic opens with a more detailed rationale. Other alternatives build on the exact arithmetic described in section Exact Arithmetic and the inexact arithmetic described in section Inexact Arithmetic.

Specification

We assume that Section 6.2.3 ("Implementation restrictions") of R5RS remains essentially as it stands. The text of this SRFI describes the differences between the proposed additions and changes to R5RS and/or SRFI 70.

External Representations

This SRFI adds the following external representations to Scheme:

Implementations that use binary floating point representations of real numbers should represent x|p using a p-bit significand if practical, or by a greater precision if a p-bit significand is not practical, or by the largest available precision if p or more bits of significand is not practical within the implementation.

Note: The precision of a significand should not be confused with the number of bits used to represent the significand. In the IEEE floating point standards, for example, the significand's most significant bit is implicit in single and double precision but is explicit in extended precision. Whether that bit is implicit or explicit does not affect the mathematical precision. In implementations that use binary floating point, the default precision can be calculated by calling the following procedure:

    (define (precision)
      (do ((n 0 (+ n 1))
           (x 1.0 (/ x 2.0)))
        ((= 1.0 (+ 1.0 x)) n)))

Note: When the underlying floating-point representation is IEEE double precision, the |p suffix should not be omitted for all cases: Denormalized numbers have diminished precision, and therefore should carry a |p suffix with the actual width of the signficand.

The number->string procedure is generalized over the R5RS version to support the x|p notation.

To be consistent with this SRFI, the write procedure would be required to write inexact numbers in the external representation produced by number->string with one argument.

Implementations are not required to use floating-point representations, but every implementation is required to designate a subset of its inexact reals as flonums, and to convert certain external representations into flonums.

The R6RS section on the lexical structure of numerical tokens should read as as follows:

The following rules for <num R>, <complex R>, <real R>, <ureal R>, <uinteger R>, and <prefix R> should be replicated for R = 2, 8, 10, and 16. There are no rules for <decimal 2>, <decimal 8>, and <decimal 16>, which means that numbers containing decimal points or exponents or mantissa widths must be in decimal radix.

<num R>  --> <prefix R> <complex R>
<complex R> --> <real R> | <real R> @ <real R>
    | <real R> + <ureal R> i | <real R> - <ureal R> i
    | <real R> + i | <real R> - i
    | + <ureal R> i | - <ureal R> i | + i | - i
<real R> --> <sign> <ureal R>
<ureal R>  -->  <uinteger R>
    |  <uinteger R> / <uinteger R>
    |  <decimal R> <mantissa width>
    |  inf.0 | nan.0
<decimal 10>  -->  <uinteger 10> <suffix>
    |  . <digit 10>+ #* <suffix>
    |  <digit 10>+ . <digit 10>* #* <suffix>
    |  <digit 10>+ #* . #* <suffix>
<uinteger R>  -->  <digit R>+ #*
<prefix R>  -->  <radix R> <exactness>
    |  <exactness> <radix R>

<suffix>  -->  <empty>
    |  <exponent marker> <sign> <digit 10>+
<exponent marker>  -->  e  |  s  |  f  |  d  |  l
<mantissa width> -> <empty>
    | | <digit 10>+
<sign>  -->  <empty>  |  +  |  -
<exactness>  -->  <empty>  |  #i  |  #e
<radix 2>  -->  #b
<radix 8>  -->  #o
<radix 10>  -->  <empty>  |  #d
<radix 16>  -->  #x
<digit 2>  -->  0  |  1
<digit 8>  -->  0  |  1  |  2  |  3  |  4  |  5  |  6  |  7
<digit 10>  -->  0  |  1  |  2  |  3  |  4  |  5  |  6  |  7  |  8  |  9
<digit 16>  -->  <digit 10>  |  a  |  b  |  c  |  d  |  e  |  f

If a <decimal 10> does not contain a non-empty <mantissa width> and does not contain one of the exponent markers s, f, d, or l, but does contain a decimal point or the exponent marker e, then it is an external representation for a flonum. Furthermore inf.0, +inf.0, -inf.0, nan.0, +nan.0, and -nan.0 are external representations for flonums. Some or all of the other external representations for inexact reals may also represent flonums, but that is not required by this SRFI.

If a <decimal 10> contains a non-empty <mantissa width> or one of the exponent markers s, f, d, or l, then it represents an inexact number, but does not necessarily represent a flonum.

Safe and Unsafe Mode

This SRFI allows a Scheme implementation to run code in one of two global modes: "safe" and "unsafe" mode. These affect the fixnum and the flonum operations. In safe mode, these operations must check that their arguments are actually fixnums or flonums respectively, or perform possible additional checking as required by the specifications of the operations. In unsafe mode, these operations must provide no such checking. This distinction allows an implementation to generate efficient numerical code at the cost of avoiding run-time checking. The R6RS should require a Scheme implementation to provide the safe mode.

This SRFI uses the phrase "all bets are off" to describe that the behavior of a procedure is unspecified for certain arguments in unsafe mode. Specifically, a procedure call for which all bets are off is free to crash the system.

Equivalence of Objects

The R6RS specification of eqv? for numbers should be changed as follows.

The eqv? procedure returns #t if:

The eqv? procedure returns #f if:

Numerical Type Predicates

The following description is a revised form of the description given in SRFI 70. Changes from SRFI 70 are highlighted in red.

procedure: number? obj
procedure: complex? obj
procedure: real? obj
procedure: rational? obj
procedure: integer? obj

These numerical type predicates can be applied to any kind of argument, including non-numbers. They return #t if the object is of the named type, and otherwise they return #f. In general, if a type predicate is true of a number then all higher type predicates are also true of that number. Consequently, if a type predicate is false of a number, then all lower type predicates are also false of that number.

If z is an inexact a complex number, then `(real? z)' is true if and only if `(zero? (imag-part z))' and `(exact? (imag-part z))' are both true.

If x is a real number, then `(rational? x)' is true if and only if there exist exact integers k1 and k2 such that `(= x (/ k1 k2))' and `(= (numerator x) k1)' and `(= (denominator x) k2)' are all true. Thus infinities and NaNs are not rational numbers.

If x is an inexact real number, then `(integer? x)' is true if and only if
    (and (finite? x) (= x (round x)))
If q is a rational number, then `(integer? q)' is true if and only if `(= (denominator q) 1)' is true.
(complex? 3+4i)                        ==>  #t
(complex? 3)                           ==>  #t
(real? 3)                              ==>  #t
(real? -2.5+0.0i)                      ==>  #f
(real? -2.5+0i)                        ==>  #t
(real? -2.5)                           ==>  #t
(real? #e1e10)                         ==>  #t
(real? -inf.0)                         ==>  #t
(real? +nan.0)                         ==>  #t
(rational? -inf.0)                     ==>  #f
(rational? +nan.0)                     ==>  #f
(rational? 6/10)                       ==>  #t
(rational? 6/3)                        ==>  #t
(integer? 3+0i)                        ==>  #t
(integer? 3.0)                         ==>  #t
(integer? 8/4)                         ==>  #t

(number? +nan.0)                       ==>  #t
(complex? +nan.0)                      ==>  #t
(complex? +inf.0)                      ==>  #t
(real? +nan.0)                         ==>  #t
(rational? +nan.0)                     ==>  #f
(integer? -inf.0)                      ==>  #f

Note: The behavior of these type predicates on inexact numbers is unreliable, because any inaccuracy may affect the result.

Note: In many implementations the rational? procedure will be the same as real?, and the complex? procedure will be the same as number?, but unusual implementations may be able to represent some irrational numbers exactly or may extend the number system to support some kind of non-complex numbers.

procedure: exact? z
procedure: inexact? z

These numerical predicates provide tests for the exactness of a quantity. For any Scheme number, precisely one of these predicates is true.

(exact? 5)                   ==>  #t
(inexact? +inf.0)            ==>  #t

Generic Conversions

procedure: exact->inexact z
procedure: inexact->exact z

Exact->inexact returns an inexact representation of z. The value returned is the inexact number that is numerically closest to the argument. If an exact argument has no reasonably close inexact equivalent, then a violation of an implementation restriction may be reported.

Inexact->exact returns an exact representation of z. The value returned is the exact number that is numerically closest to the argument. If an inexact argument has no reasonably close exact equivalent, then a violation of an implementation restriction may be reported.

These procedures implement the natural one-to-one correspondence between exact and inexact integers throughout an implementation-dependent range.

Exact->inexact and inexact->exact are idempotent.

procedure: real->flonum x

Real->flonum returns a flonum representation of x, which must be a real number. The value returned is a flonum that is numerically closest to the argument.

Rationale: The flonums are a subset of the inexact reals, but may be a proper subset. The real->flonum procedure converts an arbitrary real to the flonum type required by flonum-specific procedures.

Note: If flonums are represented in binary floating point, then implementations are strongly encouraged to break ties by preferring the floating point representation whose least significant bit is zero.

Numerical Input and Output

procedure: number->string z
procedure: number->string z radix
procedure: number->string z radix precision

Radix must be an exact integer, either 2, 8, 10, or 16. If omitted, radix defaults to 10. If a precision is specified, then z must be an inexact complex number, precision must be an exact positive integer, and the radix must be 10. The procedure number->string takes a number and a radix and returns as a string an external representation of the given number in the given radix such that

(let ((number number)
      (radix radix))
  (eqv? number
        (string->number (number->string number
                                        radix)
                        radix)))

is true. It is an error if no possible result makes this expression true.

If a precision is specified, then the representations of the inexact real components of the result, unless they are infinite or NaN, specify an explicit <mantissa width> p, and p is the least pprecision for which the above expression is true.

If z is inexact, the radix is 10, and the above expression and condition can be satisfied by a result that contains a decimal point, then the result contains a decimal point and is expressed using the minimum number of digits (exclusive of exponent, trailing zeroes, and mantissa width) needed to make the above expression and condition true [Burger, Dybvig 1996; Clinger 1990]; otherwise the format of the result is unspecified.

The result returned by number->string never contains an explicit radix prefix.

Note: The error case can occur only when z is not a complex number or is a complex number with a non-rational real or imaginary part.

Rationale: If z is an inexact number represented using binary floating point, and the radix is 10, then the above expression is normally satisfied by a result containing a decimal point. The unspecified case allows for infinities, NaNs, and representations other than binary floating point.

procedure: string->number string
procedure: string->number string radix

Returns a number of the maximally precise representation expressed by the given string. Radix must be an exact integer, either 2, 8, 10, or 16. If supplied, radix is a default radix that may be overridden by an explicit radix prefix in string (e.g. "#o177"). If radix is not supplied, then the default radix is 10. If string is not a syntactically valid notation for a number, then string->number returns #f.

(string->number "100")                 ==>  100
(string->number "100" 16)              ==>  256
(string->number "1e2")                 ==>  100.0
(string->number "15##")                ==>  1500.0
(string->number "+inf.0")              ==>  +inf.0
(string->number "-inf.0")              ==>  -inf.0
(string->number "+nan.0")              ==>  +nan.0

Note: The domain of string->number may be restricted by implementations in the following ways. String->number is permitted to return #f whenever string contains an explicit radix prefix. If all numbers supported by an implementation are real, then string->number is permitted to return #f whenever string uses the polar or rectangular notations for complex numbers. If all numbers are integers, then string->number may return #f whenever the fractional notation is used. If all numbers are exact, then string->number may return #f whenever an exponent marker or explicit exactness prefix is used, or if a # appears in place of a digit. If all inexact numbers are integers, then string->number may return #f whenever a decimal point is used. An implementation may return #f for "+nan.0".

Integer Division

The div, mod, div+mod, and quotient+remainder procedures are new and not in SRFI 70. The definitions of quotient and remainder are as in R5RS, not as in SRFI 70.

For various kinds of arithmetic (fixnum, flonum, exact, inexact, and generic), this SRFI provides operations for performing integer division. Their variants differ mainly in the domains they operate on, not in substance. The following specification is of the general variant. Later sections will link back to this one where appropriate.

procedure: div+mod x1 x2
library procedure: div x1 x2
library procedure: mod x1 x2
These implement integer division and remainder on real numbers. div returns an integer; mod returns a real: div+mod returns two values, an integer and a real.
(div x1 x2)                  ==> nd
(mod x1 x2)                  ==> xm
(div+mod x1 x2)              ==> (div x1 x2) (mod x1 x2) ; two return values
where:
  1. x1 = nd * x2 + xm.
  2. 0 <= xm < x2 if x2 > 0
  3. x2/2 <= xm < -x2/2 if x2 < 0
Moreover:
(div x1 0)                  ==> 0

Rationale: These operations generalize integer division to real numbers. Moreover, even on integers they are better suited than quotient and remainder to implement modular reduction. For details, see the paper by Egner et. al [Egner et al. 2004].

library procedure: quotient+remainder n1 n2
library procedure: quotient n1 n2
library procedure: remainder n1 n2
library procedure: modulo n1 n2

These procedures implement number-theoretic (integer) division. n2 should be non-zero. Quotient, remainder, and modulo return integers; quotient+remainder returns two integers.

(quotient+remainder n1 n2)         ==> (quotient n1 n2) (remainder n1 n2) ; two return values

If n1/n2 is an integer:

(quotient n1 n2)                   ==> n1/n2
(remainder n1 n2)                  ==> 0
(modulo n1 n2)                     ==> 0
If n1/n2 is not an integer:
(quotient n1 n2)                   ==> nq
(remainder n1 n2)                  ==> nr
(modulo n1 n2)                     ==> nm

where nq is n1/n2 rounded towards zero, 0 < |nr| < |n2|, 0 < |nm| < |n2|, nr and nm differ from n1 by an integral multiple of n2, nr has the same sign as n1, and nm has the same sign as n2.

From this we can conclude that for integers n1 and n2 with n2 not equal to 0,

     (= n1 (+ (* n2 (quotient n1 n2))
           (remainder n1 n2)))
                                       ==>  #t
provided all numbers involved in that computation are exact.
(quotient+remainder 13 4)              ==>  3 1
(modulo 13 4)                          ==>  1
(remainder 13 4)                       ==>  1

(quotient+remainder -13 4)             ==>  -3 -1
(modulo -13 4)                         ==>  3
(remainder -13 4)                      ==>  -1

(quotient+remainder -13 4)             ==>  -3 1
(modulo 13 -4)                         ==>  -3
(remainder 13 -4)                      ==>  1

(modulo -13 -4)                        ==>  -1
(remainder -13 -4)                     ==>  -1

Quotient, remainder and modulo can easily be defined through div and mod:

(define (sign n)
  (cond
    ((positive? n) 1)
    ((negative? n) -1)
    (else 0)))

(define (quotient n1 n2)
  (* (sign n1) (sign n2) (div (abs n1) (abs n2))))

(define (remainder n1 n2)
  (* (sign n1) (mod (abs n1) (abs n2))))

(define (modulo n1 n2)
  (* (sign n2) (mod (* (sign n2) n1) (abs n2))))

Fixnums

A subset range of the exact integers is denoted as the set of fixnums. Conversely, a fixnum is an exact integer whose value lies within this fixnum range.

Every implementation must define its fixnum range as a closed interval [lo, hi] such that lo and hi are (mathematical) integers with lo <= 0 < 1 <= hi. Every mathematical integer within an implementation's fixnum range must correspond to an exact integer that is representable within the implementation. The fixnum operations of an implementation will perform arithmetic modulo hi-lo+1.

procedure: fixnum? obj

This returns #t if obj is a fixnum, and otherwise returns #f.

procedure: least-fixnum
procedure: greatest-fixnum

These procedures return lo and hi, the minimum and the maximum value of the fixnum range, respectively.

We will use fx, fx1 and fx2 as metavariables that range over fixnums.

If an argument to the following procedures that corresponds to a fixnum metavariable is not actually a fixnum, then these procedures signal an error, unless the implementation is running in unsafe mode, in which case all bets are off.

procedure: fx= fx1 fx2
procedure: fx> fx1 fx2
procedure: fx< fx1 fx2
procedure: fx>= fx1 fx2
procedure: fx<= fx1 fx2

These procedures return #t if their arguments are (respectively): equal, monotonically increasing, monotonically decreasing, monotonically nondecreasing, or monotonically nonincreasing, #f otherwise.

library procedure: fxzero? fx
library procedure: fxpositive? fx
library procedure: fxnegative? fx
library procedure: fxodd? fx
library procedure: fxeven? fx

These numerical predicates test a fixnum for a particular property, returning #t or #f. Fxzero? tests if the number is zero, fxpositive? tests if it is greater than zero, fxnegative? tests if it is less than zero, fxodd? tests if it is odd, fxeven? tests if it is even.

library procedure: fxmax fx1 fx2
library procedure: fxmin fx1 fx2

These procedures return the maximum or minimum of their arguments.

procedure: fx+ fx1 fx2
procedure: fx- fx1 fx2
procedure: fx* fx1 fx2

These procedures return the unique fixnum that is congruent mod hi-lo+1 to the sum, difference, or product of their arguments.

library procedure: fxabs fx

This procedure returns `(fx- 0 fx)' if fx is negative, fx otherwise.

procedure: fxdiv+mod fx1 fx2
library procedure: fxdiv fx1 fx2
library procedure: fxmod fx1 fx2
library procedure: fxquotient fx1 fx2
library procedure: fxmodulo+remainder fx1 fx2
library procedure: fxmodulo fx1 fx2
library procedure: fxremainder fx1 fx2

These procedures implement number-theoretic integer division modulo hi-lo+1. See the section on Integer Division.

library procedure: fxgcd fx1 fx2
library procedure: fxlcm fx1 fx2

These procedures return the greatest common divisor or least common multiple of their arguments modulo hi-lo+1.

procedure: fxbitwise-not fx

Returns the fixnum which is the one's-complement of its argument. congruent mod hi-lo+1.

procedure: fxbitwise-and fx1 fx2
procedure: fxbitwise-ior fx1 fx2
procedure: fxbitwise-xor fx1 fx2

The fxbitwise-and procedure returns the fixnum which is the bit-wise "and" of the two's complement representations of its arguments modulo hi-lo+1. The fxbitwise-ior procedure returns the fixnum which is the bit-wise inclusive "or" of the two's complement representations of its arguments modulo hi-lo+1. The fxbitwise-xor procedure returns the fixnum which is the bit-wise "exclusive or" of the two's complement representations of its arguments modulo hi-lo+1.

procedure: fxarithmetic-shift fx1 fx2

This procedure conceptually shifts the two's complement representation of fx1 fx2 bits left when fx2 > 0, and -fx2 bits right when fx2 < 0, extending the sign. (It returns fx1 when fx2 = 0.) It returns the result of that shift congruent mod hi-lo+1.

Flonums

Implementations are not required to use floating-point representations, but every implementation is required to designate a subset of its inexact reals as flonums, and to convert certain external representations into flonums. The flonums are closed under the flonum operations described in this section.

procedure: flonum? obj

This returns #t if obj is a flonum, and otherwise returns #f.

We will use fl, fl1 and fl2 as metavariables that range over flonums, and ifl, ifl1 and ifl2 as metavariables that range over integral flonums, i.e. flonums that represent integers.

If an argument to the following procedures that corresponds to a (integral) flonum metavariable is not actually a (integral) flonum, then these procedures signal an error, unless the implementation is running in unsafe mode, in which case all bets are off.

procedure: fl= fl1 fl2
procedure: fl< fl1 fl2
procedure: fl<= fl1 fl2
procedure: fl> fl1 fl2
procedure: fl>= fl1 fl2

These procedures return #t if their arguments are (respectively): equal, monotonically increasing, monotonically decreasing, monotonically nondecreasing, or monotonically nonincreasing, #f otherwise. These predicates are required to be transitive.

(fl= +inf.0 +inf.0)           ==>  #t
(fl= -inf.0 +inf.0)           ==>  #f
(fl= -inf.0 -inf.0)           ==>  #t
(fl= 0.0 -0.0)                ==>  unspecified
(fl< 0.0 -0.0)                ==>  unspecified
(fl= +nan.0 fl)               ==>  unspecified
(fl= +nan.0 fl)               ==>  unspecified
(fl< +nan.0 fl)               ==>  unspecified
The following behavior is strongly recommended but not required by R6RS:

(fl= 0.0 -0.0)                 ==>  #t
(fl< -0.0 0.0)                 ==>  #f
(fl= +nan.0 +nan.0)            ==>  #f

procedure: flinteger? fl
procedure: flzero? fl
procedure: flpositive? fl
procedure: flnegative? fl
procedure: flodd? ifl
procedure: fleven? ifl
procedure: flnan? fl

These numerical predicates test a flonum for a particular property, returning #t or #f. Flinteger? tests it if the number is an integer, flzero? tests if it is fl= to zero, flpositive? tests if it is greater than zero, flnegative? tests if it is less than zero, flodd? tests if it is odd, fleven? tests if it is even, flnan? tests if it is a NaN.

(flnegative? -0.0) ==> #f

Note: `(flnegative? -0.0)' must return #f, else it would lose the correspondence with (fl< -0.0 0.0), which is #f according to the IEEE standards.

procedure: flmax fl1 fl2
procedure: flmin fl1 fl2

These procedures return the maximum or minimum of their arguments.

procedure: fl+ fl1 fl2
procedure: fl- fl1 fl2
procedure: fl* fl1 fl2
procedure: fl/ fl1 fl2

These procedures return the flonum sum, difference, product, or quotient of their flonum arguments. In general, they should return the flonum that best approximates the mathematical sum, difference, or quotient of their arguments. (For implementations that represent flonums as IEEE binary floating point numbers, the meaning of "best" is reasonably well-defined by the IEEE standards.)

For undefined quotients, fl/ behaves as specified by the IEEE standards:

(fl/ 1.0 0.0)  ==> +inf.0
(fl/ -1.0 0.0) ==> -inf.0
(fl/ 0.0 0.0)  ==> +nan.0

procedure: flabs fl

This returns the absolute value of its argument.

procedure: fldiv+mod fl1 fl2
library procedure: fldiv fl1 fl2
library procedure: flmod fl1 fl2
library procedure: flquotient ifl1 ifl2
library procedure: flmodulo+remainder ifl1 ifl2
library procedure: flmodulo ifl1 ifl2
library procedure: flremainder ifl1 ifl2

These procedures implement number-theoretic integer division. See the section on Integer Division. For zero divisors, the procedures may return +nan.0 or some meaningless flonum.

library procedure: flgcd ifl1 ifl2
library procedure: fllcm ifl1 ifl2

These procedures return the greatest common divisor or least common multiple of their arguments.

library procedure: flnumerator fl
library procedure: fldenominator fl

These procedures return the numerator or denominator of their argument as a flonum; the result is computed as if the argument was represented as a fraction in lowest terms. The denominator is always positive. The denominator of 0 is defined to be 1.

(flnumerator +inf.0)           ==>  +inf.0
(flnumerator -inf.0)           ==>  -inf.0
(fldenominator +inf.0)         ==>  1.0
(fldenominator -inf.0)         ==>  1.0
(flnumerator 0.75)             ==>  3.0 ; example
(fldenominator 0.75)           ==>  4.0 ; example
The following behavior is strongly recommended but not required by R6RS:
(flnumerator -0.0)             ==> -0.0

procedure: flfloor fl
procedure: flceiling fl
procedure: fltruncate fl
procedure: flround fl

These procedures return integral flonums for flonum arguments that are not infinities or NaNs. For such arguments, flfloor returns the largest integral flonum not larger than fl. Flceiling returns the smallest integral flonum not smaller than fl. Fltruncate returns the integral flonum closest to fl whose absolute value is not larger than the absolute value of fl. Flround returns the closest integral flonum to fl, rounding to even when fl is halfway between two integers.

Rationale: Flround rounds to even for consistency with the default rounding mode specified by the IEEE floating point standard.

(flfloor +inf.0)                       ==>  +inf.0
(flceiling -inf.0)                     ==>  -inf.0

procedure: flexp fl
procedure: fllog fl
procedure: flsin fl
procedure: flcos fl
procedure: fltan fl
procedure: flasin fl
procedure: flatan1 fl
procedure: flatan2 fl1 fl2

These procedures compute the usual transcendental functions. Fllog computes the natural logarithm of fl (not the base ten logarithm). Flasin, Flacos, and Flatan1 compute arcsine (sin-1), arccosine (cos-1), and arctangent (tan-1), respectively. `(flatan1 fl1 fl2)' computes the arc tangent of fl1/fl2. The range of flatan1 and flatan2 lies between -pi/2 and pi/2, both inclusive if -0.0 is distinguished, both exclusive otherwise.

In general, the mathematical functions log, arcsine, arccosine, and arctangent are multiply defined. The value of log z is defined to be the one whose imaginary part lies in the range from -pi (exclusive) to pi (inclusive). With log defined this way, the values of sin-1 fl, cos-1 z, and tan-1 z are according to the following formulae:

sin-1 z = -i log (i z + sqrt(1 - z2))

cos-1 z = pi / 2 - sin-1 z

tan-1 z = (log (1 + i z) - log (1 - i z)) / (2 i)

If the function has a real-valued limit as its argument tends toward positive infinity, then that is the value returned by the function applied to +inf.0. If the function has a real-valued limit as its argument tends toward negative infinity, then that is the value returned by the function applied to -inf.0.

In the event that these formulae do not yield a real result for the given arguments, the result may be +nan.0, or may be some meaningless flonum.

Implementations that use IEEE binary floating point arithmetic are encouraged to follow the relevant standards for these procedures.

Specifically, the range of `(flatan2 x y)' is as in the following table. The asterisk (*) indicates that the entry applies to implementations that distinguish minus zero.

y Condition x Condition Range of result
y = 0.0 x > 0.0 0.0
* y = +0.0 x > 0.0 +0.0
* y = -0.0 x > 0.0 -0.0
y > 0.0 x > 0.0 0.0 < result< pi/2
y > 0.0 x = 0.0 pi/2
y > 0.0 x < 0.0 pi/2 < result< pi
y = 0.0 x < 0 pi
* y = +0.0 x < 0.0 +pi
* y = -0.0 x < 0.0 -pi
y < 0.0 x < 0.0 -pi< result< -pi/2
y < 0.0 x = 0.0 -pi/2
y < 0.0 x > 0.0 -pi/2 < result< 0.0
y = 0.0 x = 0.0 undefined
* y = +0.0 x = +0.0 +0.0
* y = -0.0 x = +0.0 -0.0
* y = +0.0 x = -0.0 +pi
* y = -0.0 x = -0.0 -pi

The above specification follows the branch cut specification of Common Lisp in [Pitman 1996], and [Steele 1990], which in turn cites [Penfield 1981]; refer to these sources for more detailed discussion of branch cuts, boundary conditions, and implementation of these functions.

(flexp +inf.0)                ==> +inf.0
(flexp -inf.0)                ==> 0.0
(fllog +inf.0)                ==> +inf.0
(fllog 0.0)                   ==> -inf.0
(fllog -0.0)                  ==> unspecified ; if -0.0 is distinguished
(fllog -inf.0)                ==> +nan.0
(flatan1 -inf.0)              ==> -1.5707963267948965
(flatan1 +inf.0)              ==> 1.5707963267948965

procedure: flsqrt fl

Returns the principal square root of z. For a negative argument, the result may be +nan.0, or may be some meaningless flonum. With flexp and fllog defined as above, the value of `(flsqrt fl)' is defined as `(flexp (fl/ (fllog fl) 2.0))'.

(flsqrt +inf.0)               ==>  +inf.0

procedure: flexpt fl1 fl2

Returns fl1 raised to the power fl2. For nonzero fl1

fl1fl2 = ez2 log z1

0fl is 1 if z = 0, and 0 if fl is positive. Otherwise, the result may be +nan.0, or may be some meaningless flonum.

Fixnum/Flonum Conversions

procedure: fixnum->flonum fx
procedure: flonum->fixnum fl

The fixnum->flonum and flonum->fixnum procedures provide for explicit conversions between fixnums and flonums. The fixnum->flonum procedure returns, for a flonum argument, its numerically closest fixnum. The flonum->fixnum procedure returns, for a fixnum argument, its numerically closest flonum. Programmers should understand that these procedures cannot be expected to return results that are numerically equal to their arguments.

For example, suppose the fixnum range is [-8388608,8388607] and flonums are represented in IEEE double precision. Then

(fixnum->flonum 8388608)         ==> 8388608.0
(flonum->fixnum 3.14159265)      ==> 3
(flonum->fixnum -inf.0)          ==> -8388608
(flonum->fixnum 1e20)            ==> 8388607

If the argument to fixnum->flonum is not a fixnum, or the argument to flonum->fixnum is NaN or not a flonum, then an error is signalled, unless the implementation is running in unsafe mode, in which case all bets are off.

Exact Arithmetic

The exact arithmetic provides generic operations on exact numbers; these operations correspond to their mathematical counterparts. The exact numbers include rationals of arbitrary precision, and exact rectangular complex numbers. A rational number with a denominator of 1 is indistinguishable from its numerator. An exact rectangular complex number with a zero imaginary part is indistinguishable from its real part.

procedure: exnumber? ex
procedure: excomplex? ex
procedure: exrational? ex
procedure: exinteger? ex

These numerical type predicates can be applied to any kind of argument, including non-numbers. They return #t if the object is an exact number of the named type, and otherwise return #f. In general, if a type predicate is true of a number then all higher type predicates are also true of that number. Consequently, if a type predicate is false of a number, then all lower type predicates are also false of that number.

We will use ex, ex1, ex2, and ex3 as metavariables that range over the exact complex numbers, ef, ef1, ef2, and ef3 as metavariables that range over the exact rational numbers, and ei, ei1, ei2, and ei3 as metavariables that range over the exact integers.

If an argument to the following procedures that corresponds to an exact (rational, integer) metavariable is not actually an exact (rational, integer) number, then these procedures signal an error, even in unsafe mode.

procedure: ex= ex1 ex2 ex3 ...
procedure: ex> ef1 ef2 ef3 ...
procedure: ex< ef1 ef2 ef3 ...
procedure: ex>= ef1 ef2 ef3 ...
procedure: ex<= ef1 ef2 ef3 ...

These procedures return #t if their arguments are (respectively): equal, monotonically increasing, monotonically decreasing, monotonically nondecreasing, or monotonically nonincreasing #f otherwise.

library procedure: exzero? ex
library procedure: expositive? ef
library procedure: exnegative? ef
library procedure: exodd? ei
library procedure: exeven? ei

These numerical predicates test an exact number for a particular property, returning #t or #f. Exzero? tests if the number is ex= to zero, expositive? tests if it is greater than zero, exnegative? tests if it is less than zero, exodd? tests if it is odd, exeven? tests if it is even.

library procedure: exmax ef1 ef2 ...
library procedure: exmin ef1 ef2 ...

These procedures return the maximum or minimum of their arguments.

procedure: ex+ ex1 ex2 ...
procedure: ex* ex1 ex2 ...

These procedures return the sum or product of their arguments.

procedure: ex- ex1 ex2 ...
procedure: ex- ex
procedure: ex/ ex1 ex2 ...
procedure: ex/ ex

With two or more arguments, these procedures return the difference or quotient of their arguments, associating to the left. With one argument, however, they return the additive or multiplicative inverse of their argument. Ex/ signals an error if a divisor is 0.

library procedure: exabs ef

This procedure returns the absolute value of its argument.

procedure: exdiv+mod ef1 ef2
library procedure: exdiv ef1 ef2
library procedure: exmod ef1 ef2
library procedure: exquotient ei1 ei2
library procedure: exmodulo+remainder ei1 ei2
library procedure: exmodulo ei1 ei2
library procedure: exremainder ei1 ei2

These procedures implement number-theoretic integer division. See the section on Integer Division. For zero divisors, these procedures signal an error.

library procedure: exgcd ei1 ei2 ...
library procedure: exlcm ei1 ei2 ...

These procedures return the greatest common divisor or least common multiple of their arguments.

library procedure: exnumerator ef
library procedure: exdenominator ef

These procedures return the numerator or denominator of their argument. The result is computed as if the argument was represented as a fraction in lowest terms. The denominator is always positive. The denominator of 0 is defined to be 1.

procedure: exfloor ef
procedure: exceiling ef
procedure: extruncate ef
procedure: exround ef

These procedures return exact integers. Exfloor returns the largest integer not larger than ef. Exceiling returns the smallest integer not smaller than ef. Extruncate returns the integer closest to ef whose absolute value is not larger than the absolute value of ef. Exround returns the closest integer to ef, rounding to even when ef is halfway between two integers.

procedure: exexpt ef1 ei2

Returns ef1 raised to the power ei2. 0ei is 1 if ei = 0 and 0 if ei is positive. Otherwise, this procedure signals an error.

procedure: exmake-rectangular ex1 ex2
procedure: exreal-part ex
procedure: eximag-part ex

The arguments of exmake-rectangular must be exact rationals. Suppose z is a complex number such that z = ef1 + ef2*i. Then:

(exmake-rectangular ef1 ef2) ==> z
(exreal-part z)              ==> ef1
(eximag-part z)              ==> ef2

Inexact Arithmetic

The inexact arithmetic provides generic operations on inexact numbers. The inexact numbers include inexact reals and inexact complex numbers, both of which are distinguishable from the exact numbers. The inexact complex numbers include the flonums, and the procedures described here behave consistently with the corresponding flonum procedures if passed flonum arguments.

procedure: innumber? obj
procedure: incomplex? obj
procedure: inreal? obj
procedure: inrational? obj
procedure: ininteger? obj

These numerical type predicates can be applied to any kind of argument, including non-numbers. They return #t if the object is an inexact number of the named type, and otherwise they return #f. In general, if a type predicate is true of a number then all higher type predicates are also true of that number. Consequently, if a type predicate is false of a number, then all lower type predicates are also false of that number.

We will use in, in1, in2, and in3 as metavariables that range over the inexact numbers, ir, ir1, ir2, and ir3 as metavariables that range over the inexact real numbers, if, if1, if2, and if3 as metavariables that range over the inexact rationals. and ii, ii1, ii2, and ii3 as metavariables that range over the inexact integers.

If an argument to the following procedures that corresponds to an inexact metavariable is not actually an inexact number, then these procedures signal an error, even in unsafe mode. The same holds true for arguments corresponding to inexact real metavariables.

procedure: in= in1 in2 in3 ...
procedure: in> ir1 ir2 ir3 ...
procedure: in< ir1 ir2 ir3 ...
procedure: in>= ir1 ir2 ir3 ...
procedure: in<= ir1 ir2 ir3 ...

These procedures return #t if their arguments are (respectively): equal, monotonically increasing, monotonically decreasing, monotonically nondecreasing, or monotonically nonincreasing #f otherwise. These predicates are required to be transitive.

library procedure: inzero? in
library procedure: inpositive? ir
library procedure: innegative? ir
library procedure: inodd? ii
library procedure: ineven? ii
library procedure: innan? in

These numerical predicates test an inexact number for a particular property, returning #t or #f. Inzero? tests if the number is in= to zero, inpositive? tests if it is greater than zero, innegative? tests if it is less than zero, inodd? tests if it is odd, ineven? tests if it is even, innan? tests if it is a NaN.

library procedure: inmax ir1 ir2 ...
library procedure: inmin ir1 ir2 ...

These procedures return the maximum or minimum of their arguments.

procedure: in+ in1 in2 ...
procedure: in* in1 in2 ...

These procedures return the sum or product of their arguments.

procedure: in- in1 in2 ...
procedure: in- in
procedure: in/ in1 in2 ...
procedure: in/ in

With two or more arguments, these procedures return the difference or quotient of their arguments, associating to the left. With one argument, however, they return the additive or multiplicative inverse of their argument.

library procedure: inabs in

This procedure returns the absolute value of its argument.

procedure: indiv+mod ir1 ir2
library procedure: indiv ir1 ir2
library procedure: inmod ir1 ir2
library procedure: inquotient ii1 ii2
library procedure: inmodulo+remainder ii1 ii2
library procedure: inmodulo ii1 ii2
library procedure: inremainder ii1 ii2

These procedures implement number-theoretic integer division. See the section on Integer Division. For zero divisors, these procedures signal an error or may return some meaningless inexact number.

library procedure: ingcd ii1 ii2 ...
library procedure: inlcm ii1 ii2 ...

These procedures return the greatest common divisor or least common multiple of their arguments.

library procedure: innumerator if
library procedure: indenominator if

These procedures return the numerator or denominator of their argument. The result is computed as if the argument was represented as a fraction in lowest terms. The denominator is always positive. The denominator of 0 is defined to be 1.

procedure: infloor ir
procedure: inceiling ir
procedure: intruncate ir
procedure: inround ir

These procedures return inexact integers for real arguments that are not infinities or NaNs. For such arguments, infloor returns the largest integer not larger than ir. Inceiling returns the smallest integer not smaller than ir. Intruncate returns the integer closest to in whose absolute value is not larger than the absolute value of in. Inround returns the closest integer to in, rounding to even when in is halfway between two integers.

Rationale: Round rounds to even for consistency with the default rounding mode specified by the IEEE floating point standard.

procedure: inexp in
procedure: inlog in
procedure: insin in
procedure: incos in
procedure: intan in
procedure: inasin in
procedure: inatan in
procedure: inatan ir1 ir2

These procedures compute the usual transcendental functions. Inlog computes the natural logarithm of in (not the base ten logarithm). Inasin, Inacos, and Inatan compute arcsine (sin-1), arccosine (cos-1), and arctangent (tan-1), respectively. The two-argument variant of Inatan computes `(inangle (inmake-rectangular ir1 ir2))' (see below).

In general, the mathematical functions log, arcsine, arccosine, and arctangent are multiply defined. The value of log z is defined to be the one whose imaginary part lies in the range from -pi (inclusive if -0.0 is distinguished, exclusive otherwise) to pi (inclusive). With log defined this way, the values of sin-1 z, cos-1 z, and tan-1 z are according to the following formulae:

sin-1 z = -i log (i z + sqrt(1 - z2))

cos-1 z = pi / 2 - sin-1 z

tan-1 z = (log (1 + i z) - log (1 - i z)) / (2 i)

If the function has a real-valued limit as its argument tends toward positive infinity, then that is the value returned by the function applied to +inf.0. If the function has a real-valued limit as its argument tends toward negative infinity, then that is the value returned by the function applied to -inf.0.

In the event that these formulae do not yield a real result for the given arguments, the result may be +nan.0, or may be some meaningless inexact number.

Specifically, the range of `(inatan x y)' is as in the following table. The asterisk (*) indicates that the entry applies to implementations that distinguish minus zero.

y Condition x Condition Range of result
y = 0.0 x > 0.0 0.0
* y = +0.0 x > 0.0 +0.0
* y = -0.0 x > 0.0 -0.0
y > 0.0 x > 0.0 0.0 < result< pi/2
y > 0.0 x = 0.0 pi/2
y > 0.0 x < 0.0 pi/2 < result< pi
y = 0.0 x < 0 pi
* y = +0.0 x < 0.0 +pi
* y = -0.0 x < 0.0 -pi
y < 0.0 x < 0.0 -pi< result< -pi/2
y < 0.0 x = 0.0 -pi/2
y < 0.0 x > 0.0 -pi/2 < result< 0.0
y = 0.0 x = 0.0 undefined
* y = +0.0 x = +0.0 +0.0
* y = -0.0 x = +0.0 -0.0
* y = +0.0 x = -0.0 +pi
* y = -0.0 x = -0.0 -pi

The above specification follows the branch cut specification of Common Lisp in [Pitman 1996], and [Steele 1990], which in turn cites [Penfield 1981]; refer to these sources for more detailed discussion of branch cuts, boundary conditions, and implementation of these functions.

procedure: insqrt in

Returns the principal square root of z. For a negative argument, the result may be +nan.0, or may be some meaningless inexact number. With inlog defined as above, the value of `(insqrt in)' is defined as `(inexp (in/ (inlog in) 2.0))'.

procedure: inexpt in1 in2

Returns in1 raised to the power in2. For nonzero in1

in1in2 = ein2 log in1

0.0z is 1 if z = 0.0, and 0.0 if `(inreal-part z)' is positive. Otherwise, this procedure reports a violation of an implementation restriction or returns an unspecified number.

procedure: inmake-rectangular ir1 ir2
procedure: inmake-polar ir1 ir2
procedure: inreal-part in
procedure: inimag-part in
procedure: inmagnitude in
procedure: inangle in

Suppose in1, in2, in3, and in4 are inexact rational numbers, and z is a complex number, such that z = in1 + in2*i = in3 * ei*in4. Then (inexactly):

(inmake-rectangular in1 in2) ==> z
(inmake-rectangular in3 in4) ==> z
(inreal-part z)              ==> in1
(inimag-part z)              ==> in2
(inmagnitude z)              ==> |in3|
(inangle z)                  ==> inangle

where -pi <= inangle <= pi with inangle = in4 + 2pi n for some integer n.

(inangle -1.0)         ==> pi
(inangle -1.0+0.0)     ==> pi
(inangle -1.0-0.0)     ==> -pi ; if -0.0 is distinguished

R5RS-style Generic Arithmetic

In this model of arithmetic, the section on exactness reads as follows:

Scheme numbers are either exact or inexact. A number is exact if it was written as an exact constant or was derived from exact numbers using only exact operations. A number is inexact if it is infinite, if it was written as an inexact constant, if it was derived using inexact ingredients, or if it was derived using inexact operations. The generic operations all implement inexact operations. Thus inexactness is a contagious property of a number.

Each exact number corresponds to a single mathematical number. It is the programmer's responsibility to avoid using exact numbers with magnitude or precision too large to be represented in the implementation. For inexact numbers, it is the programmer's responsibility to avoid using complex numbers with magnitude too large to be represented in the implementation.

It is the programmer's responsibility to avoid using numbers with magnitude or significant too large to be represented in the implementation.

If two implementations produce exact results for a computation that did not involve inexact intermediate results or the results of numerical predicates, the two ultimate results will be mathematically equivalent. This is generally not true of computations involving inexact numbers because approximate methods such as floating point arithmetic may be used, but it is the duty of each implementation to make the result as close as practical to the mathematically ideal result.

Rational operations such as + should always produce exact results when given exact arguments. If the operation is unable to produce an exact result, then it may either report the violation of an implementation restriction or it may silently coerce its result to an inexact value. See section 6.2.3.

With the exception of inexact->exact exact-round, exact-ceiling, exact-floor, and exact-truncate, the operations described in this section must return inexact results when given any inexact arguments.

The procedures described here behave consistently with the inexact arithmetic if passed inexact arguments, and consistently with the exact arithmetic if passed exact arguments.

We will use z, z1, z2, and z3 as metavariables that range over the complex numbers, x, x1, x2, and x3 as metavariables that range over the real numbers, q, q1, q2, and q3 as metavariables that range over the rationals. and n, n1, n2, and n3 as metavariables that range over the inexact integers.

If an argument to the following procedures that corresponds to a metavariable is not in the set specified for that metavariable, then these procedures signal an error, even in unsafe mode.

procedure: = z1 z2 z3 ...
procedure: < x1 x2 x3 ...
procedure: > x1 x2 x3 ...
procedure: <= x1 x2 x3 ...
procedure: >= x1 x2 x3 ...

These procedures return #t if their arguments are (respectively): equal, monotonically increasing, monotonically decreasing, monotonically nondecreasing, or monotonically nonincreasing #f otherwise.

(= +inf.0 +inf.0)           ==>  #t
(= -inf.0 +inf.0)           ==>  #f
(= -inf.0 -inf.0)           ==>  #t
For any real number x that is neither infinite nor NaN:
(< -inf.0 x +inf.0))        ==>  #t
(> +inf.0 x -inf.0))        ==>  #t

These predicates are required to be transitive.

Note: The traditional implementations of these predicates in Lisp-like languages are not transitive.

Note: While it is not an error to compare inexact numbers using these predicates, the results may be unreliable because a small inaccuracy may affect the result; this is especially true of = and zero?. When in doubt, consult a numerical analyst.

library procedure: zero? z
library procedure: positive? x
library procedure: negative? x
library procedure: odd? n
library procedure: even? n
library procedure: nan? z

These numerical predicates test a number for a particular property, returning #t or #f. See note above. Zero? tests if the number is = to zero, positive? tests if it is greater than zero, negative? tests if it is less than zero, odd? tests if it is odd, even? tests if it is even, nan? tests if it is a NaN.

(positive? +inf.0)            ==>  #t
(negative? -inf.0)            ==>  #t

library procedure: max x1 x2 ...
library procedure: min x1 x2 ...

These procedures return the maximum or minimum of their arguments.

(max 3 4)                              ==>  4    ; exact
(max 3.9 4)                            ==>  4.0  ; inexact
For any real number x:
(max +inf.0 x)                         ==>  +inf.0
(min -inf.0 x)                         ==>  -inf.0

Note: If any argument is inexact, then the result will also be inexact (unless the procedure can prove that the inaccuracy is not large enough to affect the result, which is possible only in unusual implementations). If min or max is used to compare numbers of mixed exactness, and the numerical value of the result cannot be represented as an inexact number without loss of accuracy, then the procedure may report a violation of an implementation restriction.

procedure: + z1 ...
procedure: * z1 ...

These procedures return the sum or product of their arguments.

(+ 3 4)                                ==>  7
(+ 3)                                  ==>  3
(+)                                    ==>  0
(+ +inf.0 +inf.0)                      ==>  +inf.0
(+ +inf.0 -inf.0)                      ==>  +nan.0

(* 4)                                  ==>  4
(*)                                    ==>  1
(* 5 +inf.0)                           ==>  +inf.0
(* -5 +inf.0)                          ==>  -inf.0
(* +inf.0 +inf.0)                      ==>  +inf.0
(* +inf.0 -inf.0)                      ==>  -inf.0
(* 0 +inf.0)                           ==>  +nan.0
For any real number x that is neither infinite nor NaN:
(+ +inf.0 x)                           ==>  +inf.0
(+ -inf.0 x)                           ==>  -inf.0
(+ +nan.0 x)                           ==>  +nan.0
(* +nan.0 x)                           ==>  +nan.0

If any of these procedures are applied to mixed non-rational real and non-real complex arguments, they either report a violation of an implementation restriction or return an unspecified number.

procedure: - z1 z2
procedure: - z
optional procedure: - z1 z2 ...
procedure: / z1 z2
procedure: / z
optional procedure: / z1 z2 ...

With two or more arguments, these procedures return the difference or quotient of their arguments, associating to the left. With one argument, however, they return the additive or multiplicative inverse of their argument.

(- 3 4)                                ==>  -1
(- 3 4 5)                              ==>  -6
(- 3)                                  ==>  -3
(- +inf.0 +inf.0)                      ==>  +nan.0

(/ 3 4 5)                              ==>  3/20
(/ 3)                                  ==>  1/3
(/ 0.0)                                ==>  +nan.0
(/ 1.0 0)                              ==>  +inf.0
(/ -1 0.0)                             ==>  -inf.0
(/ +inf.0)                             ==>  0.0
(/ 0 0.0)                              ==>  +nan.0 unspecified
(/ 0.0 0)                              ==>  +nan.0
(/ 0.0 0.0)                            ==>  +nan.0

library procedure: abs x

Abs returns the absolute value of its argument.

(abs -7)                               ==>  7
(abs -inf.0)                           ==>  +inf.0

procedure: div+mod x1 x2
library procedure: div x1 x2
library procedure: mod x1 x2
library procedure: quotient n1 n2
library procedure: modulo+remainder n1 n2
library procedure: modulo n1 n2
library procedure: remainder n1 n2

These procedures implement number-theoretic integer division. See the section on Integer Division.

library procedure: gcd r1 n1 ...
library procedure: lcm r1 n1 ...

Note: This is the R5RS definition.

These procedures return the greatest common divisor or least common multiple of their arguments. The result is always non-negative.

For exact integer arguments, these procedures are the familiar number theoretic operators:

(gcd 32 -36)                           ==>  4
(gcd)                                  ==>  0
(lcm 32 -36)                           ==>  288
(lcm 32.0 -36)                         ==>  288.0 ; inexact
(lcm)                                  ==>  1

procedure: numerator q
procedure: denominator q

These procedures return the numerator or denominator of their argument; the result is computed as if the argument was represented as a fraction in lowest terms. The denominator is always positive. The denominator of 0 is defined to be 1.

(numerator (/ 6 4))                    ==>  3
(denominator (/ 6 4))                  ==>  2
(denominator
  (exact->inexact (/ 6 4)))            ==>  2.0

procedure: floor x
procedure: ceiling x
procedure: truncate x
procedure: round x

These procedures accept finite real numbers and return integers. These procedures return inexact integers on inexact arguments that are not infinities or NaNs, and exact integers on exact rational arguments. For such arguments, floor returns the largest integer not larger than x. Ceiling returns the smallest integer not smaller than x. Truncate returns the integer closest to x whose absolute value is not larger than the absolute value of x. Round returns the closest integer to x, rounding to even when x is halfway between two integers.

Rationale: Round rounds to even for consistency with the default rounding mode specified by the IEEE floating point standard.

Note: If the argument to one of these procedures is inexact, then the result will also be inexact. If an exact value is needed, the result should be passed to the inexact->exact procedure.

(floor -4.3)                           ==>  -5.0
(ceiling -4.3)                         ==>  -4.0
(truncate -4.3)                        ==>  -4.0
(round -4.3)                           ==>  -4.0

(floor 3.5)                            ==>  3.0
(ceiling 3.5)                          ==>  4.0
(truncate 3.5)                         ==>  3.0
(round 3.5)                            ==>  4.0  ; inexact

(round 7/2)                            ==>  4    ; exact
(round 7)                              ==>  7

(floor +inf.0)                         ==>  +inf.0
(ceiling -inf.0)                       ==>  -inf.0

library procedure: rationalize x y

Rationalize returns the simplest rational number differing from x by no more than y. A rational number r1 is simpler than another rational number r2 if r1 = p1/q1 and r2 = p2/q2 (in lowest terms) and |p1|<= |p2| and |q1| <= |q2|. Thus 3/5 is simpler than 4/7. Although not all rationals are comparable in this ordering (consider 2/7 and 3/5) any interval contains a rational number that is simpler than every other rational number in that interval (the simpler 2/5 lies between 2/7 and 3/5). Note that 0 = 0/1 is the simplest rational of all.

(rationalize
  (inexact->exact .3) 1/10)            ==> 1/3    ; exact
(rationalize .3 1/10)                  ==> #i1/3  ; inexact

(rationalize +inf.0 3)                 ==>  +inf.0
(rationalize +inf.0 +inf.0)            ==>  +nan.0
(rationalize 3 +inf.0)                 ==>  0

procedure: exp z
procedure: log z
procedure: sin z
procedure: cos z
procedure: tan z
procedure: asin z
procedure: acos z
procedure: atan z
procedure: atan y x

These procedures compute the usual transcendental functions. Log computes the natural logarithm of z (not the base ten logarithm). Asin, acos, and atan compute arcsine (sin-1), arccosine (cos-1), and arctangent (tan-1), respectively. The two-argument variant of `atan' computes (angle (make-rectangular x y)) (see below), even in implementations that don't support general complex numbers.

In general, the mathematical functions log, arcsine, arccosine, and arctangent are multiply defined. The value of log z is defined to be the one whose imaginary part lies in the range from -pi (exclusive) (inclusive if -0.0 is distinguished, exclusive otherwise) to pi (inclusive).

The value of log z for non-real z is defined in terms of log on real numbers as

log z = log |z| + xanglei

where xangle is the angle of z as specified below.

With log defined this way, the values of sin-1 z, cos-1 z, and tan-1 z are according to the following formulae:

sin-1 z = -i log (i z + sqrt(1 - z2))

cos-1 z = pi / 2 - sin-1 z

tan-1 z = (log (1 + i z) - log (1 - i z)) / (2 i)

If the function has a real-valued limit as its argument tends toward positive infinity, then that is the value returned by the function applied to +inf.0. If the function has a real-valued limit as its argument tends toward negative infinity, then that is the value returned by the function applied to -inf.0.

In the event that these formulae do not yield a real result for the given arguments, the result may be +nan.0, or may be some meaningless inexact number.

Specifically, the range of `(atan x y)' is as in the following table. The asterisk (*) indicates that the entry applies to implementations that distinguish minus zero.

y Condition x Condition Range of result
y = 0.0 x > 0.0 0.0
* y = +0.0 x > 0.0 +0.0
* y = -0.0 x > 0.0 -0.0
y > 0.0 x > 0.0 0.0 < result< pi/2
y > 0.0 x = 0.0 pi/2
y > 0.0 x < 0.0 pi/2 < result< pi
y = 0.0 x < 0 pi
* y = +0.0 x < 0.0 +pi
* y = -0.0 x < 0.0 -pi
y < 0.0 x < 0.0 -pi< result< -pi/2
y < 0.0 x = 0.0 -pi/2
y < 0.0 x > 0.0 -pi/2 < result< 0.0
y = 0.0 x = 0.0 undefined
* y = +0.0 x = +0.0 +0.0
* y = -0.0 x = +0.0 -0.0
* y = +0.0 x = -0.0 +pi
* y = -0.0 x = -0.0 -pi
* y = +0.0 x = 0 pi/2
* y = -0.0 x = 0 -pi/2

The above specification follows the branch cut specification of Common Lisp in [Pitman 1996], and [Steele 1990], which in turn cites [Penfield 1981]; refer to these sources for more detailed discussion of branch cuts, boundary conditions, and implementation of these functions.

(exp +inf.0)                   ==> +inf.0
(exp -inf.0)                   ==> 0.0
(log +inf.0)                   ==> +inf.0
(log 0.0)                      ==> -inf.0
(log -inf.0)                   ==> +nan.0 unspecified
(atan -inf.0)                  ==> -1.5707963267948965
(atan +inf.0)                  ==> 1.5707963267948965
(log -1.0+0.0i)                ==> 0.0+pii
(log -1.0-0.0i)                ==> 0.0-pii ; if -0.0 is distinguished

The functions sin, cos, tan, asin, and acos either return +nan.0 or report a violation of an implementation restriction when given +inf.00, -inf.0, or +nan.0 as an argument.

procedure: sqrt z

Returns the principal square root of z. For real rational z, the result will have either positive real part, or zero real part and non-negative imaginary part. With exp and log defined as above, the value of `(sqrt z)' `(exp (/ (log z) 2.0))'.

(sqrt -5)                   ==>  0.0+2.23606797749979i
(sqrt +inf.0)               ==>  +inf.0
(sqrt -inf.0)               ==>  +nan.0 unspecified

procedure: expt z1 z2

Returns z1 raised to the power z2. For nonzero z1

z1z2 = ez2 log z1

0z is 1 if z = 0, and 0 if `(real-part z)' is positive. Otherwise, this procedure reports a violation of an implementation restriction or returns an unspecified number. 0^0 is 1.

For inexact arguments not both zero

(define (expt z1 z2) (exp (* (if (zero? z1) (real-part z2) z2) (log z1))))
(expt 0.0 z)
returns 1.0 for z equal to 0;
returns 0.0 for z having positive real part (including +/0.);
returns +/0. for z having negative real part (including -/0.); and
returns 0/0. or reports a violation of an implementation restriction otherwise.
(expt 5 3)                  ==>  125
(expt 5 -3)                 ==>  1/125
(expt 5 0)                  ==>  1
(expt 0 5)                  ==>  0
(expt 0 5+.0000312i)        ==>  0
(expt 0 -5)                 ==>  +inf.0 unspecified
(expt 0 -5+.0000312i)       ==>  +inf.0 unspecified
(expt 0 0)                  ==>  +nan.0 
(expt 0.0 0.0)              ==>  +nan.0 1.0

procedure: make-rectangular x1 x2
procedure: make-polar x3 x4
procedure: real-part z
procedure: imag-part z
procedure: magnitude z
procedure: angle z

These procedures are part of every implementation that supports general complex numbers. Suppose x1, x2, x3, and x4 are real numbers and z is a complex number such that

z = x1 + i x2 = x3 ei x4

Then

(make-rectangular x1 x2)               ==> z
(make-polar x3 x4)                     ==> z
(real-part z)                          ==> x1
(imag-part z)                          ==> x2
(magnitude z)                          ==> |x3|
(angle z)                              ==> xangle

where -pi <<= xangle <= pi with xangle = in4 + 2pi n for some integer n.

(angle -1)                    ==> pi
(angle +inf.0)                ==> 0.0
(angle -inf.0)                ==> pi
(angle -1.0+0.0)              ==> pi
(angle -1.0-0.0)              ==> -pi ; if -0.0 is distinguished

Rationale: Magnitude is the same as abs for a real argument, but abs must be present in all implementations, whereas magnitude need only be present in implementations that support general complex numbers.

procedure: bitwise-and ei1 ei2 ...
procedure: bitwise-ior ei1 ei2 ...
procedure: bitwise-xor ei1 ei2 ...

The bitwise-and procedure returns the exact integer which is the bit-wise "and" of the two's complement representations of its arguments. The bitwise-ior procedure returns the exact integer which is the bit-wise inclusive "or" of the two's complement representations of its arguments. The bitwise-xor procedure returns the exact integer which is the bit-wise "exclusive or" of the two's complement representations of its arguments.

procedure: arithmetic-shift ei1 ei2

This conceptually shifts the two's complement representation of ei1 ei2 bits left when ei2 > 0, and -ei2 bits right when ei2 < 0, extending the sign. It returns ei1 when ei2 = 0.

Generic Exact Arithmetic

This specification of exact arithmetic is an alternative to the R5RS-style generic arithmetic described in the previous section.

The exact generic arithmetic provides generic operations on exact and inexact rational and complex numbers. Each inexact number, except for infinities and NaNs and complex numbers with infinite or NaN real or imaginary part, is taken to represent a unique exact rational number or exact complex number with exact rational real and imaginary parts, namely the one produced by inexact->exact. With this provision, the generic exact operations correspond to their mathematical counterparts.

Note: In this section, changes with respect SRFI 70 aren't marked specially---otherwise, the entire section would be in red.

In this model of arithmetic, the section on exactness reads as follows: Changes from the description of exactness in the previous section are highlighted in red.

Scheme numbers are either exact or inexact. A number is exact if it was written as an exact constant or was derived from exact numbers using only exact operations. A number is inexact if it was written as an inexact constant, if it was derived using inexact ingredients, or if it was derived using inexact operations. The generic operations all implement inexact exact operations. Consequently, inexactness exactness is a contagious property of a number.

It is the programmer's responsibility to avoid using numbers with magnitude or significand too large to be represented in the implementation.

If two implementations produce exact results for a computation using only the operations described in this section that did not involve inexact intermediate results or the results of numerical predicates inputs (which include the literals representing inexact numbers), the two ultimate results will be mathematically equivalent. This is generally not true of computations involving inexact numbers because approximate methods such as floating point arithmetic may be used, but it is the duty of each implementation to make the result as close as practical to the mathematically ideal result. The transition from exactness to inexactness is always explicit through exact->inexact, real->flonum (or indirectly through I/O). Consequently, inexactness cannot implicitly contaminate a computation that started out with exact numbers and does not use the (explicitly) inexact operations.

Rational operations such as + should always produce exact results when given exact arguments. If the operation is unable to produce an exact result, then it may either report the violation of an implementation restriction or it may silently coerce its result to an inexact value. See section 6.2.3.

With the exception of exact->inexact the operations described in this section must return inexact results when given any inexact arguments.

Rationale: This departure from the R5RS semantics abandons the notion that the same program may compute either exactly or inexactly, depending on the input numbers. Each generic procedure always performs the same computation, which is in contrast to R5RS where the generic procedures perform different operations depending on their inputs. This means that the generic operations reliably fulfill the standard algebraic laws. For a more detailed treatment of the subject, refer to the paper by Egner et al. [Egner et al. 2004].

We will use zf, zf1, zf2, and z3 as metavariables that range over the complex numbers, excluding NaNs, infinities, or complex numbers with NaN or infinite real or imaginary part, xf, xf1, xf2, and xf3 as metavariables that range over the real numbers excluding NaNs and infinities, q, q1, q2, and q3 as metavariables that range over the rational numbers, and n, n1, n2, and n3 as metavariables that range over the integers.

If an argument to the following procedures that corresponds to one of the above metavariables is not actually a rational or complex number with rational real and imaginary part, these procedures signal an error, even in unsafe mode.

procedure: = zf1 zf2 zf3 ...
procedure: > xf1 xf2 xf3 ...
procedure: < xf1 xf2 xf3 ...
procedure: >= xf1 xf2 xf3 ...
procedure: <= xf1 xf2 xf3 ...

These procedures return #t if their arguments are (respectively): equal, monotonically increasing, monotonically decreasing, monotonically nondecreasing, or monotonically nonincreasing.

library procedure: zero? zf
library procedure: positive? xf
library procedure: negative? xf
library procedure: odd? n
library procedure: even? n

These numerical predicates test an exact number for a particular property, returning #t or #f. Zero? tests if the number is = to zero, positive? tests if it is greater than zero, negative? tests if it is less than zero, odd? tests if it is odd, even? tests if it is even.

library procedure: max xf1 xf2 ...
library procedure: min xf1 xf2 ...

These procedures return the maximum or minimum of their arguments.

procedure: + zf1 zf2 ...
procedure: * zf1 zf2 ...

These procedures return the sum or product of their arguments.

procedure: - zf1 zf2 ...
procedure: - zf
procedure: / zf1 zf2 ...
procedure: / zf

With two or more arguments, these procedures return the difference or quotient of their arguments, associating to the left. With one argument, however, they return the additive or multiplicative inverse of their argument. / signals an error if a divisor is 0.

library procedure: abs xf

This procedure returns the absolute value of its argument.

procedure: div+mod xf1 xf2
library procedure: div xf1 xf2
library procedure: mod xf1 xf2
library procedure: quotient n1 n2
library procedure: modulo+remainder n1 n2
library procedure: modulo n1 n2
library procedure: remainder n1 n2

These procedures implement number-theoretic integer division. See the section on Integer Division.

library procedure: gcd n1 n2 ...
library procedure: lcm n1 n2 ...

These procedures return the greatest common divisor or least common multiple of their arguments.

library procedure: numerator q
library procedure: denominator q

These procedures return the numerator or denominator of their argument. The result is computed as if the argument was represented as a fraction in lowest terms. The denominator is always positive. The denominator of 0 is defined to be 1.

procedure: floor xf
procedure: ceiling xf
procedure: truncate xf
procedure: round xf

These procedures return exact integers. Floor returns the largest integer not larger than xf. Ceiling returns the smallest integer not smaller than xf. Truncate returns the integer closest to xf whose absolute value is not larger than the absolute value of xf. Round returns the closest integer to xf, rounding to even when xf is halfway between two integers.

procedure: expt q n

Returns q raised to the power n. 0n is 1 if n = 0 and 0 if n is positive. Otherwise, this procedure signals an error.

procedure: make-rectangular xf1 xf2
procedure: real-part zf
procedure: imag-part zf

Suppose zf is a complex number such that zf = x1 + xf2*i. Then:

(make-rectangular xf1 xf2) ==> zf
(real-part zf)            ==> xf1
(imag-part zf)            ==> xf2

procedure: bitwise-not n

Returns the one's-complement of its argument.

procedure: bitwise-and n1 n2 ...
procedure: bitwise-ior n1 n2 ...
procedure: bitwise-xor n1 n2 ...

The bitwise-and procedure returns the exact integer which is the bit-wise "and" of the two's complement representations of its arguments. The bitwise-ior procedure returns the exact integer which is the bit-wise inclusive "or" of the two's complement representations of its arguments. The bitwise-xor procedure returns the exact integer which is the bit-wise "exclusive or" of the two's complement representations of its arguments.

procedure: arithmetic-shift n1 n2

This conceptually shifts the two's complement representation of n1 n2 bits left when n2 > 0, and -n2 bits right when n2 < 0, extending the sign. It returns n1 when n2 = 0.

Design Rationale

Background and Rationale concerning two issues of R6RS arithmetic (written by William D Clinger, 29 July 2005)

(Most experts who read this note will want to start with the section Problems With Flow Analysis, thereby skipping the history and background information that is intended to make this note more self-contained and accessible.)

Lucier's Proposal

In 1998, for the Scheme workshop before ICFP '98 in Baltimore, Brad Lucier proposed several changes that were intended to bring Scheme's inexact arithmetic into line with the IEEE floating point standards and with other recommended practice for transcendental functions and complex arithmetic. Most of Lucier's proposals would have applied only to implementations that use IEEE floating point for inexact arithmetic, and would thus act as recommendations, much like the appendices on inexact arithmetic that were published with the IEEE standard for Scheme. A few of Lucier's proposals would have required changes to the Scheme standards themselves, however.

Few of the workshop attendees knew enough about the IEEE floating point standards to discuss Lucier's proposal. A straw poll was taken, and came out 31-0 in favor of bringing Scheme into line with IEEE floating point and with current practice, trusting experts to work out the details.

Here are the highlights of Lucier's proposal, taken verbatim from my notes on the 1998 workshop:

The behavior of eqv? on inexact numbers would change. If x and y are inexact reals represented as IEEE floating point numbers, then `(eqv? x y)' would be true if and only if x and y are equal and have the same base, sign, number of bits in the exponent, number of bits in the significand, and the same biased exponents and significands. For example, `(eqv? +0. -0.) would be false, as would `(eqv? 1e8 1d8)' in an implementation for which 1d8 has more precision than 1e8. In most implementations `(eqv? x y)' would be computed by a bit-level comparison of the floating point representations for x and y.

`(real? 4.3+0.i)' and `(real? 4.3-0.i)' would be false, although `(real? 4.3+0i)' and `(real? 4.3-0i)' would remain true (assuming an implementation allows the real and imaginary parts of a complex number to have a different exactness, which is not required by the Scheme standards and would not be required by Lucier's proposal).

Truncate, round, ceiling, and floor would be defined only on rationals, not on all reals. The motivation for this is that infinities and NaNs would be reals but not rationals, and there is no meaningful integer value that these procedures could return for infinities and NaNs. Similarly the first argument to rationalize would be required to be a rational.

The branch cuts for certain transcendental functions would change to conform to current practice.

Kahan reportedly would like for `(max 1 +nan.0 2)' to return 2 instead of +nan.0, but this would conflict with the guiding principle of Scheme's inexact arithmetic so I oppose this. Returning an inexact 2.0 would be consistent with Scheme's arithmetic, and would not require any changes to the Scheme standards.

The Issues

The purpose of this note is to explain why it is desirable for Scheme's reals to have an exact zero as their imaginary part.

This note also discusses whether the proposed "flonum" type should be a synonym for "inexact real", or merely a subset of the inexact reals.

Numerical Types

Scheme's numerical types are the exactness types { exact, inexact }, the tower types { integer, rational, real, complex, number }, and the Cartesian product of the exactness types with the tower types, where < t1, t2 > is regarded as a subtype of both t1 and t2.

These types have an aesthetic symmetry to them, but they are not equally important. Judging by the number of R5RS procedures whose domain is restricted to values of some numerical type, the most important numerical types are the exact integers, the integers, the rationals, the reals, and the complex numbers.

Many programmers and implementors regard R5RS's focus on those particular numerical types as something of a mistake. In practice, there is reason to believe that the most important numerical types are the exact integers, the exact rationals, the inexact reals, and the inexact complex numbers. The following section explores one of the reasons those four types are so important in practice.

Closure Properties

Scheme's types are not completely arbitrary. Each type corresponds to a set of values that turns up repeatedly as the natural domain or range of the functions that are computed by Scheme's standard procedures. The reason these types turn up so often is that they are closed under certain sets of operations.

The exact integers, for example, are closed under the integral operations of addition, subtraction, and multiplication. The exact rationals are closed under the rational operations, which consist of the integral operations plus division (although we must make a special case for division by zero). The reals (and inexact reals) are closed under some (often inexact) interpretation of rational and irrational operations such as exp and sin, but are not closed under operations such as log, sqrt, and expt. The complex (and inexact complex) numbers are closed under the largest set of operations.

Representation Types

The Scheme standards give implementations considerable freedom with respect to the representation of expressed values, but the usual approach is to represent the numerical types as unions of the representation types that appear on the right hand sides of the following domain equations:

exact integer = fixnum + bignum
exact rational = fixnum + bignum + ratnum
inexact real = flonum
inexact complex = flonum + compnum
integer = fixnum + bignum + subset of flonum
rational = fixnum + bignum + ratnum + subset of flonum
real = fixnum + bignum + ratnum + flonum
complex = rectangular + compnum

These domain equations are typical of most implementations, but many variations are possible. The flonum representation type, for example, is often divided into several distinct precisions.

Representation-specific Operations

A naive implementation of Scheme's arithmetic operations is slow compared to the arithmetic operations of most other languages, mainly because most operations must perform some sort of case dispatch on the representation types of their arguments. The potential for this case dispatch arises when the type of an operation's argument is represented by a union of two or more representation types, or because the operation is required to signal an error when given an argument of an incorrect type. (The second reason can be regarded as a special case of the first.)

To make Scheme's arithmetic more efficient, many implementations provide sets of operations whose domain is restricted to a single representation type, and which are not expected to signal an error when given arguments of incorrect type.

Alternatively, or in addition, several compilers perform some kind of flow analysis that attempts to infer the representation types of expressions. When a single representation type can be inferred for each argument of an operation, and those types match the types expected by some representation-specific version of the operation, then the compiler can substitute the specific version for the more general version that was specified in the source code.

Flow Analysis

Flow analysis is performed by solving the type and interval constraints that arise from such things as:

The purpose of flow analysis (as motivated in this note) is to infer a single representation type for each argument of an operation. That places a premium on predicates and closure properties from which a single representation type can be inferred.

Problems With Flow Analysis

In practice, the most important single representation types are fixnum, flonum, and compnum. These are the representation types for which a short sequence of machine code can be generated when the representation type is known, but for which considerably less efficient code will probably have to be generated when the representation type cannot be inferred.

The fixnum representation type is not closed under any R5RS operations, so it is hard for flow analysis to infer the fixnum type from portable R5RS code. Sometimes the combination of a more general type (e.g. exact integer) and an interval (e.g. [0,n), where n is known to be a fixnum) can imply the fixnum representation type. Adding fixnum-specific operations that map fixnums to fixnums (by computing modulo 2^n, say) greatly increases the number of fixnum representation types that a compiler can infer.

The flonum representation type is not closed under operations such as sqrt and expt, so flow analysis tends to break down in the presence of those operations. This is unfortunate, because those operations are normally used only with arguments for which the result is expected to be a flonum. Adding flonum-specific versions such as flsqrt and flexpt improves the effectiveness of flow analysis.

The R5RS creates a more insidious problem by defining `(real? z)' to be true if and only if `(zero? (imag-part z))' is true. This means, for example, that -2.5+0.0i is real. If -2.5+0.0i is represented as a compnum, then the compiler cannot rely on x being a flonum during the <real_case> of `(if (real? x) <real_case> <unreal_case>)'. This problem could be fixed by writing all of the arithmetic operations so that any compnum with a zero imaginary part is converted to a flonum before it is returned, but that merely creates an analogous problem for compnum arithmetic, as explained below. A better way to fix the problem is as Lucier recommended: change the definition of Scheme's real numbers by defining `(real? z)' to be true if and only if `(imag-part z)' is an exact zero.

The compnum representation type is closed under virtually all operations, provided no operation that accepts two compnums as its argument ever returns a flonum. To work around the problem described in the paragraph above, several implementations automatically convert compnums with a zero imaginary part to the flonum representation. This practice virtually destroys the effectiveness of flow analysis for inferring the compnum representation, so it is not a good workaround. To improve the effectiveness of flow analysis, it is better to change the definition of Scheme's real numbers as described in the paragraph above.

Recommendations

To improve the effectiveness of flow analysis and to improve the efficiency of arithmetic, I recommend that the R6RS:

Flonums

When flonum-specific operations are added to Scheme, should they take any inexact real as arguments? Or should their arguments be restricted to some subtype of the inexact reals?

Another way to describe this issue is to specify that the flonum-specific operations take flonums as arguments and return flonums as results. Then the question becomes: Are the flonums coextensive with the inexact reals? Or are the flonums allowed to be a proper subset of the inexact reals?

I do not think this question is terribly important. Most current implementations have only one representation for inexact reals, and having only one representation for the inexact reals improves the effectiveness of flow analysis, so implementations that try to provide efficient arithmetic on inexact reals are likely to make "flonum" synonymous with "inexact real" even if the R6RS allows otherwise.

One reason for allowing "flonum" to be a proper subtype of "inexact real" is to give implementations freedom to experiment with unusual representation strategies. Another possibility is that an implementation might take "flonum" to mean the representation that is supported most efficiently by the hardware, e.g. IEEE double precision, while providing other precisions that might be more space-efficient for certain applications.

On the other hand, defining "flonum" to be a synonym for "inexact real" would simplify the presentation of flonum arithmetic, and would eliminate a couple of procedures that would otherwise have to be provided and somehow specified (e.g. real->flonum and flonum?).

I recommend that the R6RS define "flonum" as a synonym for "inexact real", but this is not a strong recommendation. It doesn't matter very much.

Reference Implementation

The reference implementation contains a reference implementation for the procedures described here, written in mostly R5RS Scheme, and based purely on integer and flonum arithmetic of the underlying Scheme implementation. It builds the entire numeric tower on fixnums and flonums. The code is set up to run under Scheme 48, but should be easy to adapt to other Scheme systems.

References

Acknowledgements

We thank Anton van Straaten, Kent Dybvig, Sebastian Egner, Bradley Lucier, Mike Ashley, Marc Feeley, Matthew Flatt, Manuel Serrano for direct and indirect assistance producing this draft. Special thanks to Aubrey Jaffer for his work on SRFI 70. We also thank David Van Horn, who did an very thorough job editing the original draft. All remaining errors (of which there probably are many) are our own.

Copyright

Copyright (C) William D Clinger and Michael Sperber (2005). All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


Editor: David Van Horn