Numeric Values
Summary: We examine a variety of issues pertaining to
numeric values in Scheme, including the types of numbers that Scheme
supports and some common numeric functions.
Introduction
Computer scientists write algorithms for a variety of problems. Some
types of computation, such as representation of knowledge, use
symbols and lists. Others, such as the construction of Web pages,
may involve the manipulation of strings (sequences of alphabetic
characters). However, as you've seen with some of your initial
experiments with images, a significant amount of computation involves
numbers.
One advantage of doing numeric computation with a programming language,
like Scheme, is that you can write your own algorithms to make the
computer automate repetitive tasks. As you do numeric computation
in any language, you must first discover what types of
numbers the language supports (some languages support
only integers, some only real numbers, some combinations) and what
numeric operations the language supports.
Fortunately, Scheme supports many types of numbers (as you may have
discovered in the first few labs) and a wide variety of operations on
those numbers.
Categories of Numbers
As you probably learned in secondary school, there are a
variety of kinds of numbers. The most common types are the
integers (numbers with no fractional component),
rational numbers (numbers that can be expressed
as the ratio of two integers), and real numbers
(numbers that can be plotted on a number line). Some Scheme
implementations, such as Script-Fu, the primary Scheme in GIMP,
support only integers and real numbers.
In some Scheme implementations, including the one used by default
in MediaScript, other numeric types are available, such as the
rational numbers (numbers that can be expressed
as the ratio of two integers) and complex numbers
(numbers with a possible imaginary component). Why does Script-Fu
leave out some kinds of numbers? Because the implementers did not
see a need for them. In fact, the standard language definition for
Scheme says that an implementation of the language does not have to
support all categories of numbers.
Scheme provides two basic predicates that
let us check whether or not a value has a particular
type: integer? and
real?.
> (integer? 2)
#t
> (real? 2)
#t
> (integer? 2.5)
#f
> (real? 2.5)
#t
> (integer? "two")
#f
MediaScript uses integers to represent other kinds of values, such
as images and RGB colors, so integer? will return true
for them, too. (Don't worry that you haven't seen RGB colors yet;
we'll get to them in a few days.)
> (integer? (rgb-new 0 0 0))
#t
> (integer? (image-load "/home/rebelsky/glimmer/samples/rebelsky-stalkernet.jpg"))
#t
> (integer? "/home/rebelsky/glimmer/samples/rebelsky-stalkernet.jpg")
#f
We will return to these predicates, and others, when we consider conditionals.
Exact and Inexact Numbers
Within each category of numbers, Scheme distinguishes between
exact numbers, which are guaranteed to
be calculated and stored internally with complete accuracy (no
rounding off), and approximations, also called
inexact numbers, which are stored internally
in a form that conserves the computer's memory and permits faster
computations, but allows small inaccuracies (and occasionally ones
that are not so small) to creep in. Since there's no great advantage
in obtaining an answer quickly if it may be incorrect, we shall avoid
using approximations in this course, except when the data for our
problems are themselves obtained by inexact processes of measurement.
To determine whether Scheme is representing a
particular number exactly or inexactly, use one of the
predicates exact? and
inexact?. Real numbers are never
represented exactly, and integers can be represented exactly or
inexactly. You can convert between the two representations with
exact->inexact and
inexact->exact.
> (exact? 2)
#t
> (exact? 2.0)
#f
> (inexact->exact 2.0)
2
The Scheme standard does not directly support the familiar category
of natural numbers, but we can think of them as
being just the same things as Scheme's exact non-negative integers.
Rational Numbers
All Scheme implementations support integers and reals. PLT Scheme,
the Scheme implementation we normally use in MediaScheme, also supports
rational numbers and complex numbers. PLT Scheme's support of rational
numbers may mean that you get results as a ratio of two numbers, rather
than as a decimal number. For example, when you divided 2 by 5, you
might expect to get 0.4, as in
> (/ 2 5)
0.4
In fact, you will see that result in many Scheme implementations, including
in Script-Fu, GIMP's default Scheme implementation.
However, since decimals are often approximated, PLT Scheme prefers
rationals when it makes sense to use them. Hence, in MediaScheme, you'll
see slightly different output.
> (/ 2 5)
2/5
Most of the time, it won't really matter which representation
Scheme uses. However, there are times that the results are a bit
confusing when expressed in rational form. You may have seen these
confusing results when computing average grades, as in the following.
> (/ (+ 90 80 100 23 80 75) 6)
224/3
Often, it helps to put these numbers into inexact form.
> (exact->inexact (/ (+ 90 80 100 23 80 75) 6))
74.66666666666667
PLT Scheme provides two additional procedures for working with
rational numbers, numerator
and denominator. As you might
expect, these return the numerator and denominator of a rational
number.
> (numerator 5/7)
5
> (denominator 5/7)
7
> (numerator 20/6)
10
> (denominator 20/6)
3
It's generally a bad idea to use these procedures with inexact numbers,
as PLT Scheme chooses different values than most normal people expect.
> (numerator 0.4)
3602879701896397.0
> (denominator 0.4)
9007199254740992.0
> (/ (numerator 0.4) (denominator 0.4))
0.4
Some Basic Numeric Procedures
Section 6.2.5 of the Revised5 report on
the algorithmic language Scheme lists Scheme's
primitive procedures for numbers. Read through the list at this point
to get a feel for what Scheme supports. The following notes explain
some of the subtler features of commonly used numerical procedures.
As you read about procedures, think about how you might use them in
writing color filters or in other graphical algorithms.
Warning! The output from different Scheme intpreters are
inconsistent, and sometimes even inconsistent with our expectations.
In a few cases, you may see slightly different responses than appear
in this reading.
As you've already seen, the addition and multiplication procedures,
+ and *, accept any number of arguments. You
can, for instance, ask Scheme to imitate a cash register with a command
like this one:
> (+ 1.19
.43
.43
2.59
.89
1.39
5.19
.34
)
12.45
You can call the - procedure or the
/ procedure to operate on a single
argument. The - procedure returns
the additive inverse of a single argument (its negative), the result
of subtracting it from 0.
The max procedure returns the largest of its parameters and
the min procedure returns the smallest of its parameters.
As we've already seen, max can be useful when you want to
ensure that a computation returns a value no smaller than a certain value
and min can be useful when you want to ensure that a computation
returns a value no larger than a desired maximum value.
Numeric Division
There are four procedures that relate to
division (/,
quotient,
remainder, and modulo).
You've already seen that /
can divide one value by another. If you call the
/ procedure with a single
parameter, it returns the multiplicative inverse of that parameter
(its reciprocal), the result of dividing 1 by it.
> (/ 2)
0.5
> (/ 1)
1
> (/ 0.5)
2
> (/ 0)
/: division by zero
The quotient
and remainder procedures apply only
to integers and perform the kind of division you learned in elementary
school, in which the quotient and the remainder are separated:
Thirteen divided by four is three with a remainder of one
:
> (quotient 13 4)
3
> (remainder 13 4)
1
> (quotient 1 2.5)
quotient: expects type <integer> as 2nd argument, given: 2.5; other arguments were: 1
As the final example suggests,
quotient can only be applied to
integers. The / procedure, on the
other hand, can be applied to numbers of any kind (except that you
can't use zero as a divisor) and yields a single result.
The modulo procedure is like
remainder, except that it always
yields a result that has the same sign as the divisor. In particular,
this means that when the divisor is positive and the dividend is
negative, modulo yields a positive (or zero) result.
(When can a remainder be negative? Consider -7 divided by 3. Do we
think of -7 as -2*3-1 or -3*3+2? Scheme makes the former decision
for remainder and the latter decision for modulo.)
> (remainder -13 4)
-1
> (modulo -13 4)
3
> (remainder 13 -4)
1
> (modulo 13 -4)
-3
The modulo procedure can
be particularly useful when you want to ensure that a value
falls in a certain range, and you don't just want higher
values to map to the highest value in the range. For example,
you'll find many times this semester that you want to compute
a number between 0 and 255, but end up computing something
out of that range. we can ensure that they fall within the
appropriate range with max and
min. We can get somewhat different
effects by using (modulo computed-value
256). This expression ensures that the value is between 0 and
255, but causes larger numbers to wrap-around
to become smaller numbers.
> (define blue-component 250)
> (min 255 (+ 32 blue-component))
255
> (modulo (+ 32 blue-component) 256)
26
You can also think of the value of (modulo
value modulus)
as follows: We break the number line up into
modulus-sized sections and then find the offset
of value from the start of that section.
For example, if we use a modulus of 10, the non-negative sections of
the number line would be (0..9), (10..19), (20..29), and so on and
so forth. The number 23 would be in the section (20..29). Since it's
3 bigger than 20, (moduo 23 10) is 3.
Converting Real to Integers
At times, we will have a real number and will want to convert
it to a nearby integer. For example, if you are working with
images, the components of an RGB should be integers; weird
things can happen if you try to use real numbers (not always,
but sometimes). Similarly, when specifying a row and a column
in an image, we want whole numbers for those row and column.
Scheme provides four basic procedures for
this conversion: round,
truncate,
floor, and
ceiling. You will explore the
differences between these procedures in the corresponding lab.
Warning! At times, the Scheme interpreter will
complain that it is expecting an integer but sees a real value, even
when you think you have an integer. The problem is not with you,
but with the error messages. Most of the time that the interpreter
says that it wants an integer, it really wants an
exact integer, so
use inexact->exact to get the
number in the correct form.
Comparing Numbers
Scheme provides five basic predicates for comparing numeric
values, < (less than),
<= (less than or
equal to), = (equal to),
>= (greater than or equal to),
and > (greater than). When given
two arguments, they return #t if
the indicated relation holds between the two arguments.
> (< 5 10)
#t
> (> 5 10)
#f
These predicates can also take more than two arguments. Each predicate
returns #t only if the relation holds between each pair
of adjacent arguments. If the relation fails to hold between a pair
of adjacent arguments, the predicate returns #f.
> (< 2 3 4)
#t
> (< 2 3 1)
#f
The log procedure, despite its
name, computes natural (base e) logarithms rather than common (base
ten) logarithms. You can convert a natural logarithm into a common
logarithm by dividing it by the natural logarithm of 10. In case
you've forgotten, the common logarithm of n
is the power to which you raise 10 in order to get
n
.
> (log 100)
4.605170185988092
> (/ (log 100) (log 10))
2.0
Scheme provides the standard host of trigonometric functions,
which include sin,
cosine, and
tan. When using these functions,
remember that all angles are measured in radians, not degrees.
> (sin 90)
0.8939966636005579
> (cos 90)
-0.4480736161291701
> pi
3.141592654
> (exact? pi)
#f
> (sin (/ pi 2))
1.0
> (cos (/ pi 2))
6.123031769e-17
You may wonder why the cosine of pi-over-2 (a right angle) is not 0.
It's because pi is not exactly the value of pi, but is
rounded off. However,
as scientific notation indicates, the value is pretty close to 0. (There
are sixteen leading 0's.)
We can use the trigonometric functions when we start doing more involved
drawings (e.g., they can help us draw polygons). The trigonometric
functions also provide the opportunity to do some interesting color
transformations.
Appendix: The modulo and remainder Procedures, Revisited
Many students are puzzled by both the
modulo and
remainder procedures. For
remainder, you really should think
back to middle-school math: the remainder is what's left after whole-number
division. Since modulo is the
same as remainder for positive numbers,
you can think of it that way.
More importantly, modulo provides
an interesting way of counting. Most of the time you add 1, you
follow standard protocols (1 plus 1 is 2, 2 plus 1 is 3, ...). However,
when you reach the modulus value, you go back to zero.
The following table shows the value of
remainder and
modulo for a variety of values.
| n |
-4 |
-3 |
-2 |
-1 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
| (remainder n 3) |
-1 |
0 |
-2 |
-1 |
0 |
1 |
2 |
0 |
1 |
2 |
0 |
1 |
2 |
| (remainder n 4) |
0 |
-3 |
-2 |
-1 |
0 |
1 |
2 |
3 |
0 |
1 |
2 |
3 |
0 |
| (modulo n 3) |
2 |
0 |
1 |
2 |
0 |
1 |
2 |
0 |
1 |
2 |
0 |
1 |
2 |
| (modulo n 4) |
0 |
1 |
2 |
3 |
0 |
1 |
2 |
3 |
0 |
1 |
2 |
3 |
0 |
| (modulo n 5) |
1 |
2 |
3 |
4 |
0 |
1 |
2 |
3 |
4 |
0 |
1 |
2 |
3 |