[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
My opinions about arithmetic are reflected in my language Kogut. I've
implemented a toy Scheme interpreter in it. I tried to be faithful to
R5RS, which was quite easy, but the decisions of the host language do
have some impact and I depart from R5RS in minor details.
First the original Kogut view; more about SRFI-77 below. I decided to
be quite specific about which numeric types are provided and mostly
how they behave. Leaving details unspecified makes hard to write
Types of numbers:
- Ints. The difference between fixnums and bignums is not exposed in
- Ratios. Numerator and denominator are ints. Denominator is greater
than 1. An attempt to produce the denominator of 1 yields an integer
Ints and ratios are rational numbers.
- Exact infinities, positive and negative. They are handy when a
number represents a bound or is clipped to some finite bound later.
Operations are only defined where the obvious limit exists, e.g. 1/0
is an error.
- Floats, IEEE double, including negative zero, float infinities
and not-a-number. Float infinities are numerically equal to exact
infinities but their primary purpose is different: they replace
numbers too large to be represented. Negative zero and NaN make
sense only as inexact numbers. Converting NaN to ratio or int is
an error, and both float zeros convert to the same exact zero.
Ints, ratios and floats are real numbers.
- Complex numbers. Real and imaginary part are real numbers. An
attempt to make a complex number with the imaginary part being exact
0 yields a real number instead. Note that exactnesses of the real
and imaginary parts are independent, and that float infinities may
be components of complex numbers but exact infinities may not.
C99 includes imaginary types, and William Kahan claims they are
essential for obtaining proper results for boundary conditions.
I claim that they might be essential in C where both components of
a complex number have the same type, but otherwise a value of an
imaginary type is equivalent to a complex value where the real part
is exact 0. For this reason multiplying a float by exact 0 gives
exact 0, and -0.0 + 0 = -0.0 (while -0.0 + 0.0 = 0.0).
My model is somewhat open to extensions, which would make sense in
the area of other inexact real numbers besides standard floats. Some
existing Kogut functions produce floats from exact numbers however,
and thus would have to be replaced as a whole. I don't know how to
design a sensible interface for choosing the precision dynamically.
CLISP does a very good job in returning exact results where possible,
e.g. (log 1024 4) is exact 5. I'm considering making this a requirement
(after I implement that).
* * *
Some violations of R5RS resulting from the underlying implementation
(I adopted SRFI-77 notation for +inf.0, -inf.0, +nan.0, +inf, -inf):
- (eqv? 0.0 -0.0) is #f, even though (= 0.0 -0.0) is #t and they are
both inexact. Conversely, (eqv? +nan.0 +nan.0) is #t, even though
(= +nan.0 +nan.0) is #f. Note: SRFI-77 fixes only the former.
- Complex numbers with at least real or imaginary part inexact are
considered inexact in Scheme terms. Thus 1.0+2i and 1.0+2.0i have
equal mathematical values and are both inexact, but they are not
eqv?. SRFI-77 fixes that.
Sometimes Scheme semantics forced me to make different decisions than
would be made natively:
- In R5RS real?, rational? and integer? may return #t for a complex
number with 0.0 as the imaginary part. SRFI-77 changes that, and
- rational? returns #t for finite floats, and integer? returns #t for
floats with integral values. I would reject the concept of inexact
integers at all; and while floats have rational values, they are
primarily used to approximate real numbers which are not necessarily
rational. I had to do extra work to make odd?, even? and
integer->char working for inexact integers, for no practical benefit
- min and max propagate inexactness of any element to the result.
While there is a sensible rationale of this way of ensuring that
inexactness is not lost, comparisons already produce exact booleans
from inexact arguments even if their inexactness could influence
the result (because they have no other choice, inexact control flow
doesn't make sense), so I'm not convinced that it's worth the effort.
- quotient, remainder and modulo are defined for integers including
inexct ones. Kogut has two pairs of functions which work for all
reals: Div with Mod, and Quot with Rem, as in Haskell. These
corresponding Scheme functions thus have an extended domain (Div is
missing), no problem here. It's worse with gcd, lcm, numerator and
denominator, which are in Kogut defined only for rationals and
Scheme functions must have pointless definitions for floats.
* * *
Now for SRFI-77:
I have a feeling that descriptions of specialized versions are too
repetitive, e.g. the atan2 table is shown three times. Descriptions
should be reformulated to say something like this: all these "fl"
functions are the same as the corresponding functions without the "fl"
prefix, with the domain restricted to flonums, and with the result
expressed as a flonum, which is +nan.0 if the true result was not
real, except that... and then list only possible differences. If all
results of "fx" functions are transformed into fixnums by taking the
modulo of the fixnum range, say it in one place, instead of repeating
this for each group of functions.
"defining procedures for some new operations, including integer
division and bitwise operations remainder on real numbers" -
I can't parse that; typo near the "remainder"?
"Should a minimum precision be required for fixnums or flonums?"
Probably yes for fixnums, in order to make at least some programs
using fixnum-specific functions portable.
"Should the R5RS procedures for generic arithmetic (e.g. +) remain in
R6RS?" - Yes! Perhaps requiring a full numeric tower, I'm not sure.
"Given that this SRFI suggests requiring all implementations to
support the general complex numbers, should abs (and exabs and inabs)
be removed?" - I would prefer to remove magnitude, even if the name
abs sounds bad for complex numbers, because the name "abs" is very
common among languages.
"Should `(floor +inf.0)' return +inf.0 or signal an error instead?
(Similarly for ceiling, flfloor, infloor, etc.)" - Since they return
an inexact number, they should return +inf.0. The purpose of float
infinities and NaN is to have an algorithm working without errors
when possible even if for particular inputs it happens to generate
numbers too large to be represented.
I don't like overloading div and mod for different behaviors depending
on the sign of the second argument.
"library procedure: fxmodulo+remainder fx1 fx2" - Shouldn't it be
I don't think that whole families of exact and inexact functions are
necessary. It's extremely rare that I expect inexact arguments and
I want to signal an error if they are exact (expecting only flonums
makes sense for efficiency, but not expecting inexact numbers in
general). I can't imagine using inodd? ineven?, ingcd? and inlcm? -
if numbers are known to be inexact, then these operations are poorly
defined at all.
Only a handful functions make sense in some of these variants.
For example deciding whether we want the result of floor, ceiling,
truncate and round to propagate exactness of the argument or be an
exact integer; they should accept both exact and inexact arguments.
I believe generic arithmetic should generally follow R5RS and
inexactness should be contagious. It makes no sense for (+ (sqrt 2.0) 1)
to yield a vulgar fraction.
The syntax nan.0 without the sign conflicts with an identifier.
"In unsafe mode, these operations must provide no such checking." -
It makes no sense to disallow checking for operations with undefined
behavior if a check would fail if it was done. It should be just
recommended that it's not done in order to perform faster (assuming
that Scheme will have unsafe mode at all).
__("< Marcin Kowalczyk