by Peter McGoron
This SRFI is currently in draft status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-278@nospamsrfi.schemers.org. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.
This SRFI defines procedures on Scheme numbers that aim to improve the portability and usefulness of Scheme arithmetic. These procedures include predicates with extended domains, hyperbolic and inverse hyperbolic functions, the R6RS numeric tower, and additional procedures for rounding. Also included are recommendations and justifications for mixed inexact/exact arithmetic to make complex number arithmetic more predictable.
rationalize
procedure.
This SRFI adds a few procedures that can be categorized into the following:
exact-integer?).conjugate, hyperbolic functions).real?).
The extended-domain procedures are more useful because they can be
used without invoking “it is an error” behavior. For example, in this
SRFI, (exact-integer? '()) and (nan? '())
both return #f. In addition, the rationalize
procedure has been extended to different types of intervals to make it
more useful in simplifying rational numbers while also preserving
properties such as rounding.
The common extensions are the conjugate procedure and the (inverse)
hyperbolic functions. The (inverse) hyperbolic functions are commonly
provided on Schemes that provide complex arithmetic (for example,
Gambit, Chez, Racket, Gauche, and Guile) because
the implementation of complex inverse trigonometric functions is
closely linked to the implementation of complex inverse hyperbolic
functions.
The procedure round-away
has been added to correspond to the roundTiesToAway rounding mode in
IEEE 754-2019.
The R6RS number predicates
are more useful than the R5RS
equivalents. This is because the
R6RS and
R7RS introduced signed inexact
zero to the number system. That means that 1.0+0.0i,
1.0-0.0i, and 1.0 may be distinct. The
R5RS definition says each
of these is real?: this SRFI and the
R6RS say that only
1.0 is real?, and the other two are not.
The reason for this is the interpretation of the sign of inexact zero.
A positive inexact zero is a number that is potentially above zero,
but too small for the number format, and similarly for negative
inexact zero. Hence a number with an inexact zero imaginary part is
potentially not on the real axis. This has consequences for functions
that have branch cuts on the real axis, in particular, functions like
atanh, which has a branch cut when
the real part |x| ≥ 1. For example:
(atanh 2.0+0.0i)⇒ .5493061443340549+π/2i(atanh 2.0-0.0i)⇒ .5493061443340549-π/2i(angle -1.0+0.0i)⇒ π(angle -1.0-0.0i)⇒ -π
The interpretation of unsigned zero imaginary part is more
complicated. One cannot promote unsigned imaginary zero to inexact
imaginary zero of a fixed sign. This is because the principal values
of log and angle are defined for unsigned imaginary zero such that
values are continuous when approached counterclockwise [1].
This is one of the reasons why a number with an exact (unsigned) zero
imaginary part is real?, while ones with inexact
(signed) zero imaginary part is not real?.
The number predicates are exported with the same names as the standard
predicates to make it easier to handle code that assumes the
R6RS semantics. These
procedures are likely to be added to the
R7RS Large prefixed with
strictly-. Hopefully a future standard will change the
numeric tower to have the
R6RS semantics.
Procedures
like R6RS’s
div-and-mod are not supplied.
Some proposed extensions to standard
procedures, such as
gcd on rational arguments, are also not included. These
extensions should be relegated to a version of
SRFI 141
extended to non-integers.
Requirement level verbs are in strong text.
The names of arguments have the same requirements as the R7RS. It is an error to call a procedure with a list of arguments that do not match the description given here.
When an example evaluates to an inexact number, that number is approximate.
The number that an implementation evaluates to should be close in both
parts of a complex number to that value. An exception is when one part
of a complex value is an infinity or a zero. An implementation
should evaluate to a number that has the appropriately
signed infinity or zero in that part (in the sense of eqv?).
The examples assume the existence of mixed exactness complex numbers.
The following are exported from the library defined by this SRFI.
conjugate
round-away
imaginary?
real?
rational?
integer?
exact-integer?
rationalize
nan?
sinh
cosh
tanh
asinh
acosh
atanh
(conjugate z)
Returns the complex conjugate of the procedure.
If the implementation distinguishes the sign of inexact zero in the imaginary part of a complex number, then it must flip the sign of the inexact zero.
This procedure returns an exact value given an exact argument.
Note: If non-real complex numbers are not provided, this procedure is just the identity function.
(conjugate 1.0)⇒ 1.0(conjugate 1+2i)⇒ 1-2i(conjugate 1.0+0.0i)⇒ 1.0-0.0i(conjugate 1.0-0.0i)⇒ 1.0+0.0i
(round-away
x)
Round x to an integer, with ties broken by returning the number greater in absolute value. Equivalent to the IEEE 754-2019 [2] rounding mode roundTiesToAway.
This procedure returns an exact value given an exact argument.
When passed an infinity or NaN value, that value is returned.
(round-away 2.5)⇒ 3.0(round-away 5/2)⇒ 3(round-away 2.4)⇒ 2.0(round-away -2.5)⇒ -3.0(round-away 3.6)⇒ 4.0(round-away 3.5)⇒ 4.0(round-away 3.4)⇒ 3.0
The procedure imaginary? is equivalent to
(define (imaginary? obj)
(and (complex? obj)
(eqv? (real-part obj) 0)))
(imaginary? 0.0+1.0i)⇒ #f(imaginary? -0.0+1.0i)⇒ #f(imaginary? 1.0i)⇒ #t
Returns #t iff obj is complex?
and (eqv? 0 (imag-part obj)) is #t.
(real? 1.0)⇒ #t(real? 1)⇒ #t(real? 1+0i)⇒ #t(real? 1.0+0.0i)⇒ #f(real? 1.0-0.0i)⇒ #f
Note: This procedure, along
with the rational? and
integer? defined in this
SRFI, are not compatible with the intended semantics of
the identifiers exported from the
R7RS. They are lifted
from the R6RS.
Returns #t iff obj is
real? and
there exists exact integers
k1, k2 such that
(= obj (/ k1 k2)).
Returns #t iff obj is
rational? and
(= 1 (denominator obj)).
These procedures are the same as their counterparts in the Reports,
except that they return #f when passed an object
that is not number?.
Note: Implementations are
encouraged to replace the versions of these procedures in
(scheme base) with the one defined in this SRFI.
The ones defined in this SRFI are backwards-compatible with the
versions described in the
R7RS.
(rationalize x
y)(rationalize x
delta-lower
delta-upper
[lower-inclusive?
upper-inclusive?])It is an error if delta-lower and delta-upper are not both reals. It is an error if x is not finite. It is an error if the interval described by the arguments is empty; this can only happen when delta-lower and delta-upper are both zero, and both bounds are not inclusive.
This procedure is like rationalize in the
R7RS. When
delta-lower and delta-upper is specified,
then the simplest rational between x−|delta-lower|
and x+|delta-higher| is returned.
When lower-inclusive? is true, the lower bound of the range
is inclusive; otherwise, it is exclusive.
When upper-inclusive? is true, the upper bound of the range
is inclusive; otherwise, it is exclusive. For example,
(rationalize x -1 +1 #f #t) covers the range
(x-1,x+1].
Rationale: This version of
rationalize allows for finer control over the bounds,
which is particularly important when converting floating-point
numbers into rationals. For example, one can rationalize a floating
point number f
(that is not a power of two) by considering the numbers
around it that would round to it. Let ε be the quantum of
f. In the default round-to-even mode,
the interval of rationals that round to f is (f-ε/2,f+ε/2)
when the mantissa is odd, and
[f-ε/2,f+ε/2] when the
mantissa is even. When f is a normal power of two, then the
range is [f-ε/4,f+ε/2],
because the left interval is in a different binade.
When f is a positive floating-point number in round towards
zero mode, then the interval is [f,f+ε).
A procedure called
real->rational
appears in Gauche, with similar behavior to this procedure.
Implementations are
encouraged to replace rationalize in
(scheme base) with the one specified in this SRFI.
The version in this SRFI are backwards-compatible with the
version described in the
R7RS.
Calculates the value of the hyperbolic function for z.
(sinh 0)⇒ 0(sinh +inf.0)⇒ +inf.0(sinh -inf.0)⇒ -inf.0(cosh 0)⇒ 1(cosh +inf.0)⇒ +inf.0(cosh -inf.0)⇒ +inf.0(sinh +1.0i)⇒ +.8414709848078965i
Calculates the principal value of the inverse hyperbolic function at z.
It is an error to call these values on exact numbers on a location where the procedure is undefined mathematically. The implementation should raise an exception in such a scenario.
In general, inverse hyperbolic functions are multiply valued. The following principal expressions are derived from the table in the Reports and from [1]. (For those that cannot render the math, this image shows the equations.)
The value of for non-real is defined in terms of as
where is the angle of specified as
with chosen such that . When zero is unsigned, the principal expression for angle is .
The principal expression for square root is .
With log and square root defined this way, the values of the transcendental functions relevant to this SRFI go according to the following formulæ:
(The inverse trigonometric functions are included for comparison.)
When an implementation distinguishes the sign of inexact zero in some number, and if that number lies on a branch cut, then the implementation must return a value reasonably close to the appropriate one-sided limit of the function approaching the branch cut.
(atanh 1.0+0.0i)⇒ +inf.0+.7853981633974483i(atanh 1.0-0.0i)⇒ +inf.0-.7853981633974483i(atanh -1.0+0.0i)⇒ -inf.0+.7853981633974483i(atanh -1.0-0.0i)⇒ -inf.0-.7853981633974483i(atanh 0.0+1.0i)⇒ 0.0+0.7853981633974483i(atanh 1)⇒ error(atanh -1)⇒ error(atanh 0.0-1.0i)⇒ 0.0-.7853981633974483i(acosh 0.0+0.0i)⇒ 0.+1.5707963267948966i(acosh 0.0-0.0i)⇒ 0.-1.5707963267948966i(acosh 1.0+0.0i)⇒ 0.+3.141592653589793i(acosh 1.0-0.0i)⇒ 0.-3.141592653589793i(asinh 0.0+2.0i)⇒ 1.3169578969248166+1.5707963267948966i(asinh -0.0+2.0i)⇒ -1.3169578969248166+1.5707963267948966i
If an implementation allows mixed exactness complex numbers and an implementation can determine that the imaginary or real part of a result is exact, then the implementation should return a mixed-exactness result:
(atanh 0-1.0i) ⇒ -.7853981633974483i
Some implementations of mixed real-complex arithmetic are not implemented such that the following identities are followed:
(* 1 z)⇒ z(* +i (* +i (* +i (* +i z))))⇒ z
This happens when z has an infinity/NaN in the real or imaginary part. Implementations should implement the following (for any Scheme number z, including infinities, NaNs, and inexact numbers), which allow for the above identities to work:
(* 0 z) ⇒ 0 .(/ 0 z) ⇒ 0.(+ 0 z) ⇒ z.(- 0 z)
equivalent to (- z).
For example, in an implementation that uses the above rules,
(* 1.0 +inf.0+2.0i) becomes
(make-rectangular (- (* 1.0 +inf.0) (* 0 2.0))
(+ (* 1.0 2.0) (* 0 +inf.0))) ⇒ +inf.0+2.0i
Note the expression (* 0 +inf.0). If the implementation
converted 0 to 0.0, then the implementation
would have instead evaluated to +inf.0+nan.0i.
Implementations should return exact results for the following special arguments to transcendental functions.
(sinh 0)⇒ 0(cosh 0)⇒ 1(tanh 0)⇒ 0(asinh 0)⇒ 0(acosh 1)⇒ 0(atanh 0)⇒ 0(log 1)⇒ 0(exp 0)⇒ 1(sin 0)⇒ 0(cos 0)⇒ 1(tan 0)⇒ 0(atan 0)⇒ 0(asin 0)⇒ 0
A sample R7RS implementation is available in the SRFI repository. It depends on complex numbers, inexact numbers, SRFI 144, inexact infinities, and signed zeroes. The sample implementation has been tested on Chibi Scheme, Gauche, and CHICKEN 6. A test suite that depends on those features is also available.
An implementation without complex numbers support can copy the implementations of the (inverse) hyperbolic functions from SRFI 144 instead of using the implementation in this repository. The other parts of the implementation are portable R7RS.
I thank Bradley Lucier for writing high-quality implementations of the (inverse) hyperbolic functions for Gambit, and also for his feedback on drafts of this SRFI. I have used Gambit to check that my code outputs the correct values.
I thank William Kahan for doing the hard part for me (and many other people).
[1]: Kahan, W. Branch cuts for complex elementary functions; or, Much ado about nothing's sign bit. In Iserles, A., and Powell, M. (eds.), The state of the art in numerical analysis. Clarendon Press (1987) pp 165-211.
[2]: IEEE Computer Society. IEEE Standard for Floating-Point Arithmetic (IEEE STD 754-2019). doi:10.1109/IEEESTD.2019.8766229. (2019). ISBN 978-1-5044-5924-2.
© 2026 Peter McGoron.
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 (including the next paragraph) 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.