Chapter 19: Numbers
The topics covered in this chapter are:
- The different kinds of numbers available in J
- Special numbers (infinities and indeterminates)
- Notations for writing numbers
- How numbers are displayed
19.1 Six Different Kinds of Numbers
J supports computation with numbers of these kinds:
- booleans (or truth-values)
- integers
- real (or floating-point) numbers
- complex numbers
- extended integers (that is, arbitrarily large integers exactly represented)
- rationals (that is, pairs of extended integers)
Each kind of number has its own internal representation in
memory. For example,
an array containing only the truth-values 0 and 1
is stored in a compact internal form, called "boolean",
rather
than in the floating-point form.
Similarly an array containing only (relatively small)
whole numbers is stored in a compact form
called "integer".
The choice of appropriate representation
is managed entirely automatically by the J system,
and is not normally something the programmer must be aware of.
However, there is a means of testing
the representation of a number. Here is a utility function for the purpose.
types =: 'bool';'int';'float';'complex';'ext int';'rational'
type =: > @: ({ & types) @: (1 4 8 16 64 128 & i.) @: (3 !: 0)
type 0=0 |
type 37 |
type 2.5 |
type 12345678901 |
bool |
int |
float |
float |
19.1.1 Booleans
There are built-in functions for logical computation with
boolean values.
Giving conventional names to these functions:
and =: *.
or =: +.
not =: -.
notand =: *:
notor =: +:
we can show their truth-tables:
p =: 4 1 $ 0 0 1 1
q =: 4 1 $ 0 1 0 1
p |
q |
p and q |
p or q |
not p |
p notand q |
0
0
1
1 |
0
1
0
1 |
0
0
0
1 |
0
1
1
1 |
1
1
0
0 |
1
1
1
0 |
Further logical functions can be defined in the usual way.
For example, logical implication, with the scheme
p implies q means not (p and not q)
is defined by not composed with the hook and not
imp =: not @ (and not)
p |
q |
p imp q |
0
0
1
1 |
0
1
0
1 |
1
1
0
1 |
We regard the booleans as numbers because they can
be interpreted as having arithmetic values. To illustrate,
implication has the same truth-table as
less-than-or-equal:
p imp q |
p <: q |
1
1
0
1 |
1
1
0
1 |
For another example of booleans as numbers,
the sum of the positive numbers in a list is shown by:
z =: 3 _1 4 |
b =: z > 0 |
b * z |
+/ b * z |
3 _1 4 |
1 0 1 |
3 0 4 |
7 |
19.1.2 Integers
On a 32-bit machine integers range between
_2147483648 and 2147483647.
The result of arithmetic with integers is converted to
floating-point if larger than the maximum integer.
maxint=:2147483647 |
type maxint |
z =: 1+maxint |
type z |
2147483647 |
int |
2.14748e9 |
float |
19.1.3 Floating-Point Numbers
A floating-point number is a number represented in the computer in such a way that:
(1) there may be a a fractional part as well as a whole-number part.
(2) a fixed amount of computer storage is occupied by the number, whatever the value of the number.
and therefore (3) the precision with which the number is represented is limited to
at most about 17 significant decimal digits (on a PC).
Examples of floating-point numbers are 0.25 2.5 12345678901
We will use the term "real" more or less interchangeably with "floating-point".
19.1.4 Scientific Notation
What is sometimes called "scientific notation"
is a convenient way of writing
very large or very small numbers.
For example, 1500000 may be written as 1.5e6,
meaning 1.5 * 10^6.
The general scheme is that a number written in the form XeY, where Y is a
(positive or negative) integer means (X * 10^Y).
3e2 |
1.5e6 |
1.5e_4 |
300 |
1500000 |
0.00015 |
Note that in 3e2 the letter e is not any kind of function;
it is part of the notation for writing numbers,
just as a decimal point is part of the notation.
We say that the string of characters 3 followed by e followed by 2 is a numeral
which denotes the number 300.
The string of characters 3 followed by 0
followed by 0 is another numeral denoting the same number.
Different forms of numerals provide convenient ways to express
different numbers. A number expressed by a numeral is also called a "constant"
(as opposed to a variable.)
We will come back to the topic of numerals: now we return to the topic
of different kinds of numbers.
19.1.5 Comparison of Floating-Point Numbers
Two numbers are regarded as equal if their difference is relatively small.
For example, we see that a and b have a non-zero difference,
but even so the expression a = b produces "true".
a =: 1.001 |
b =: a - 2^_45 |
a - b |
a = b |
1.001 |
1.001 |
2.84217e_14 |
1 |
If we say that the "relative difference" of two numbers is
the magnitude of the difference divided by the magnitude of the larger,
RD =: (| @: -) % (| @: >.)
then for a=b to be true, the relative difference
(a RD b) must not exceed a small value
called the "comparison tolerance" which is by default 2^_44
a RD b |
2^_44 |
a = b |
2.83933e_14 |
5.68434e_14 |
1 |
Thus to compare two numbers we need to compare relative difference with tolerance.
The latter comparison is itself strict, that is, does not involve any tolerance.
Zero is not tolerantly equal to
any non-zero number, no matter how small,
because the relative difference must be
1, and thus greater than tolerance.
tiny =: 1e_300 |
tiny = 0 |
tiny RD 0 |
1e_300 |
0 |
1 |
However, 1+tiny is tolerantly equal to 1.
tiny |
tiny = 0 |
1 = tiny + 1 |
1e_300 |
0 |
1 |
The value of the comparison tolerance currently in effect
is given by the built-in verb
9!:18 applied to a null argument. It is currently 2^_44.
9!:18 '' |
2^_44 |
5.68434e_14 |
5.68434e_14 |
Applying the built-in
verb 9!:19 to an argument y sets the tolerance to y
subsequently.
The following example shows that when the
tolerance is 2^_44, then
a = b but when the tolerance is set to zero
it is no longer the case that a = b.
(9!:19) 2^_44 |
a = b |
(9!:19) 0 |
a = b |
|
1 |
|
0 |
The tolerance queried by 9!:18 and set by 9!:19 is a global
parameter, influencing the outcome of computations with =.
A verb to apply a specified tolerance t, regardless of the global parameter,
can be written as = !. t. For example, strict (zero-tolerance) equality
can be defined by:
streq =: = !. 0
Resetting the global tolerance to the default value, we see:
(9!:19) 2^_44 |
a - b |
a = b |
a streq b |
|
2.84217e_14 |
1 |
0 |
Comparison with = is tolerant,
and so are comparisons with <, <:, >, >:, ~: and -:.
For example, the difference a-b is positive but too small to make it true that
a>b
a - b |
a > b |
2.84217e_14 |
0 |
Permissible tolerances range between 0 and 2^_35.
That is, an attempt to set the tolerance larger
than 2^_35 is an error:
(9!:19) 2^_35 |
(9!:19) 2^_34 |
|
error |
The effect of disallowing large tolerances
is that no two different integers compare equal when
converted to floating-point.
19.1.6 Complex Numbers
The square root of -1 is the imaginary number conventionally
called "i".
A complex number which is conventionally written as,
for example, 3+i4 is in J written as 3j4.
In J an imaginary number is always regarded as a
complex number with real part zero.
Thus "i", the square root of -1, can be
written 0j1.
i =: %: _1 |
i * i |
0j1 * 0j1 |
0j1 |
_1 |
_1 |
A complex number can be built from two separate real numbers by arithmetic
in the ordinary way,
or more conveniently with the built-in function j. (lowercase j dot, called
"Complex").
3 + 0j1 * 4 |
3 j. 4 |
3j4 |
3j4 |
A complex number such as 3j4 is a single number, a scalar. To extract
its real part and imaginary part separately we can use the built-in verb
+.(plus dot, called "Real/Imaginary").
To extract separately the magnitude and angle (in radians) we can use the
built-in verb *. (asterisk dot, called "Length/Angle").
+. 3j4 |
*. 3j4 |
3 4 |
5 0.927295 |
Given a magnitude and angle, we can build a complex number
by taking sine and cosine, or more conveniently with the built-in
function r. (lowercase r dot, called "Polar").
sin =: 1 & o.
cos =: 2 & o.
mag =: 5
ang =: 0.92729522 NB. radians
mag * (cos ang) + 0j1 * sin ang |
mag r. ang |
3j4 |
3j4 |
A complex constant with magnitude X and angle (in radians) Y
can be written in the form
XarY, meaning X r. Y. Similarly, if the angle is given in degrees,
we can write XadY.
5ar0.9272952 |
5ad53.1301 |
3j4 |
3j4 |
19.1.7 Extended Integers
A floating-point number, having a limited storage space in the computer's memory,
can represent an integer exactly only up to about 17 digits.
For exact computations with longer numbers, "extended integers" are available.
An "extended integer" is a number which exactly represents an integer no matter how many digits are needed.
An extended integer is written with the digits followed with the letter 'x'. Compare the following:
a =: *: 10000000001 |
b =: *: 10000000001x |
1e20 |
100000000020000000001 |
Here a is an approximation while b is an exact result.
type a |
type b |
float |
ext int |
We can see that adding 1 to a makes no difference, while adding 1 to b does make a difference:
(a + 1) - a |
(b + 1) - b |
0 |
1 |
19.1.8 Rational Numbers
A "rational number" is a single number which represents exactly the ratio of two
integers, for example, two-thirds is the ratio of 2 to 3. Two-thirds
can be written as a rational number with the notation 2r3.
The point of rationals is that they are are exact representations
using extended integers. Arithmetic with rationals gives exact results.
2r3 + 1r7 |
2r3 * 4r7 |
2r3 % 5r7 |
17r21 |
8r21 |
14r15 |
Rationals can be constructed by dividing extended integers.
Compare the following:
2 % 3 |
2x % 3x |
0.666667 |
2r3 |
A rational can be constructed from a given floating-point
number with the verb x:
A rational number can be converted to a floating-point approximation with the inverse ofx: , that is, verb x: ^: _1
float =: x: ^: _1 |
float 2r3 |
+--+--+--+
|x:|^:|_1|
+--+--+--+ |
0.666667 |
Given a rational number, its numerator and denominator can
be recovered with the verb 2 & x:, which gives a list of length 2.
nd =: 2 & x: |
nd 2r3 |
+-+-+--+
|2|&|x:|
+-+-+--+ |
2 3 |
19.1.9 Type Conversion
We have numbers of six different types:
boolean, integer, extended integer, rational, floating-point and complex.
Arithmetic can be done with a mixture of types.
For example an integer plus an extended gives an extended,
and a rational times a float gives a float.
1 + 10^19x |
1r3 * 0.75 |
10000000000000000001 |
0.25 |
The general scheme is that the six types
form a progression: from boolean
to integer to extended to rational to floating-point to complex.
We say that boolean is the simplest or "lowest" type
and complex as the most general or "highest" type
Where we have two numbers of different types,
the one of lower type is converted to match the type of the higher.
and the result is of the "higher".
type 1r3 |
type 1%3 |
z =: 1r3, 1%3 |
type z |
rational |
float |
0.333333 0.333333 |
float |
19.2 Special Numbers
19.2.1 "Infinity"
A floating-point number can (on a PC) be no larger than about 1e308,
because of the way it is stored in the computer's memory.
Any arithmetic which attempts to produce a larger result
will in fact produce a special number called "infinity"
and written _ (underscore). For example:
1e308 * 0 1 2 |
1e400 |
1 % 0 |
0 1e308 _ |
_ |
_ |
There is also
"negative infinity" written as __ (underscore underscore).
Infinity is a floating-point number:
type _
float
19.2.2 Indeterminate Numbers
Infinity is equal to infinity.
However, infinity minus infinity is
not zero but rather a special number called
"indeterminate",
written as _. (underscore dot)
Indeterminate is equal to indeterminate.
However indeterminate minus indeterminate
is not zero but indeterminate.
Computations with indeterminate may
produce surprising results.
Thus indeterminate is not to be regarded as extending
the meaning of "number" according to
some extended axiomatization of arithmetic.
Rather, the view is recommended
that the only purpose of indeterminate
is as a signal of numerical error when it occurs in the
result of a computation.
Infinity is equal to itself and
nothing else, and the same is true of
indeterminate. Thus we have a reliable (that is,
determinate) test for occurrences.
z =: 1 _ _. |
z = 1 |
z = _ |
z = _. |
1 _ _. |
1 0 0 |
0 1 0 |
0 0 1 |
19.3 Notations for Numerals
We have seen above numerals formed with the letters e, r and j, for example:
1e3, 2r3, and 3j4. Here we look at more letters for forming numerals.
A numeral written with letter p, of the form XpY means X * pi ^ Y
where pi is the familiar value 3.14159265....
pi =: 1p1 |
twopi =: 2p1 |
2p_1 |
3.14159 |
6.28319 |
0.63662 |
Similarly, a numeral written with letter x, of the form XxY means X * e ^ Y
where e is the familiar value 2.718281828....
e =: 1x1 |
2x_1 |
2 * e ^ _1 |
2.71828 |
0.735759 |
0.735759 |
These p and x forms of numeral provide a convenient way of writing constants
accurately without writing out many digits.
Finally, we can write numerals with a base other than 10.
For example the binary or base-2 number with binary digits 101 has the value 5
and can be written as 2b101.
2b101
5
The general scheme is that NbDDD.DDD is a numeral in number-base N with digits DDD.DDD .
With bases larger than 10, we will need
digits larger than 9, so we take letter 'a' as a digit with value 10, 'b' with value 11,
and so on up to 'z' with value 35.
For example, letter 'f' has digit-value 15, so in hexadecimal (base 16) the
numeral written 16bff has the value 255.
The number-base N is given in decimal.
16bff |
(16 * 15) + 15 |
255 |
255 |
One more example. 10b0.9 is evidently a base-10 number meaning "nine-tenths" and so,
in base 20, 20b0.f means "fifteen twentieths"
10b0.9 20b0.f
0.9 0.75
19.3.1 Combining the Notations
The notation-letters e, r, j ar ad p
x and b may be used in combination.
For example we can write 1r2p1
to mean "pi over two". Here are some further examples
of possible combinations.
A numeral in the form XrY denotes the number
X%Y. A numeral in the form XeYrZ denotes
the number (XeY) % Z because e is considered before r.
1.2e2 |
(1.2e2) % 4 |
1.2e2r4 |
120 |
30 |
30 |
A numeral in the form XjY denotes the complex number
(X j. Y) (that is, (X + (%: _1) * Y).
A numeral in the form XrYjZ denotes the number
(XrY) j. Z because r is considered before j
3r4 |
(3r4) j. 5 |
3r4j5 |
3r4 |
0.75j5 |
0.75j5 |
A numeral in the form XpY denotes the number
X*pi^Y. A numeral in the form XjYpZ denotes
(XjY) *pi^Z because j is considered before p.
3j4p5 |
(3j4) * pi ^ 5 |
918.059j1224.08 |
918.059j1224.08 |
A numeral in the form XbY denotes the number
Y-in-base-X. A numeral in the form XpYbZ
denotes the number Z-in-base-(XpY) because
p is considered before b.
(3*pi)+5 |
1p1b35 |
14.4248 |
14.4248 |
19.4 How Numbers are Displayed
A number is displayed by J with, by default, up to 6 or 7
significant digits.
This means that, commonly, small integers are shown exactly,
while large numbers, or numbers with many
significant digits, are shown approximately.
10 ^ 3 |
2.7182818285 |
2.718281828 * 10 ^ 7 |
1000 |
2.71828 |
2.71828e7 |
The number of significant digits used
for display is determined by a global variable
called the "print-precision". If we define
the two functions:
ppq =: 9 !: 10 NB. print-precision query
pps =: 9 !: 11 NB. print-precision set
then the expression ppq '' gives the value
of print-precision currently in effect, while
pps n will set the print-precision to n.
ppq '' |
e =: 2.718281828 |
pps 8 |
e |
6 |
2.71828 |
|
2.7182818 |
19.4.1 The "Format" Verb
There is a built-in verb ":
(doublequote colon, called "Format").
Monadic Format converts a number into a string
representing the number with the print-precision
currently in effect. In the following example,
note that a is a scalar,
while the formatted representation of a is a list of characters.
a =: 1 % 3 |
": a |
$ ": a |
0.33333333 |
0.33333333 |
10 |
The argument can be a list of numbers and the result is
a single string.
b =: 1 % 3 4 |
": b |
$ b |
$ ": b |
0.33333333 0.25 |
0.33333333 0.25 |
2 |
15 |
Dyadic Format allows more control over the
representation.
The left argument is complex: a value of
say, 8j4 will format the numbers in a width of
8 characters and with 4 decimal places.
c =: % 1 + i. 2 2 |
w =: 8j4 ": c |
$ w |
1 0.5
0.33333333 0.25 |
1.0000 0.5000
0.3333 0.2500 |
2 16 |
If the width is specified as zero (as in say 0j3)
then sufficient width is allowed. If the number of decimal places
is negative (as in 10j_3) then numbers are shown in "scientific notation"
c |
0j3 ": c |
10j_3 ": c |
1 0.5
0.33333333 0.25 |
1.000 0.500
0.333 0.250 |
1.000e0 5.000e_1
3.333e_1 2.500e_1 |
This brings us to the end of Chapter 19.
|