Current issue

Vol.26 No.4

Vol.26 No.4

Volumes

© 1984-2024
British APL Association
All rights reserved.

Archive articles posted online on request: ask the archivist.

archive/23/3

Volume 23, No.3

Functional Calculation 0 : Numerical Apéritif

Neville Holmes
School of Computing and Information Systems, University of Tasmania
Launceston 7250, Australia
Neville.Holmes@utas.edu.au

Abstract

This article is the first in a series expounding the joys of functional calculation. Functional calculation does with operations applied to functions and numbers what numerical calculation does with functions applied to numbers. The functional notation used as the vehicle in this series is provided by a freely available calculation tool called J.

This article gives a detailed illustration of a few numerical uses of the notation supported by the J interpreter. This explanation is preliminary to following articles, which will review some of that tool’s support for numerical calculation. The illustration takes a simple function and shows how J, or any other tool for functional calculation, can be used to investigate special values of the function.

Calculation

Calculation is an honourable and satisfying occupation with a long history and a rich tradition [1].

Relatively recent developments in electronic computation have led to the adoption of three classes of aids to calculation, namely

  • electronic or programmed calculators based directly on traditional calculation,
  • spreadsheet programs, and
  • complex mathematical systems.

Electronic calculators are unreliable and very poorly designed[2]. Spreadsheet programs are very powerful and useful for the kind of calculation that suits them, but clumsy and limited otherwise. The complex mathematical systems are extremely powerful, but in an elephantine way.

The kind of functional calculation described in following articles is like an enormous but sympathetic extension of traditional calculation. The extension is particularly effective in dealing with the kinds of tables to which spreadsheet programs are suited but allowing conveniently for tables of many dimensions. It is also useful for collections of tables of different kinds. The extension is of surprising expressive power so that problems which would otherwise require the might of a complex mathematical system can be tackled simply.

This article will go through some simple numerical calculations step by step, explaining them in detail. Readers will get most benefit out of the description if they get their own interpreter and themselves carry out the calculations (and any others suggested by the examples) as they are being described.

The notation used here is called simply J. Programs which evaluate J expressions as they are keyed in (because they seem to interpret the notation) are called interpreters. Interpreters for the more popular computing platforms can be downloaded free from Jsoftware[3]. The J mailing lists are active and helpful to new users, and much valuable material can be found on the J Wiki[4].

The primitive functions, those functions with symbols for names, used in this article are few, and very many more such are provided by the interpreter, and are listed and briefly explained by the interpreter’s help facilities.

The purpose of this article is to show how simple functions can be used simply to get useful results, the kind of results that show the interpreter in the light of a utility calculator. To allow this to be better appreciated, examples which involve the use of lists of numbers have been chosen.

The calculations used here as an example are based on an exploration of a function named f in the description. Any reader carrying out the example calculations can define f by keying in

   f =: -o.

but this definition will not be further explained here because the expression used to define f is a functional expression and, as such, will be explained in later articles of this series.

Finding Zeroes

Given the function f and a need to explore its behaviour, the first step is to name a useful list of test values, and then to apply the function to those values. Keying in the expression

   nn =: i.11

uses the primitive function i. (pronounced integers) to produce the first eleven non-negative integers, to which the copula =: (pronounced gets or becomes) gives the name nn.

The copula is used only for conferring names, and it is not a function as such. No result is shown because the interpreter is just naming. However any named result can be looked at just by keying in its name, in this case

   nn
0 1 2 3 4 5 6 7 8 9 10

shows nn to be a list of eleven integers.

Some general remarks are appropriate at this point. The J interpreter takes in the expressions it evaluates indented, but the results are shown unindented. For the first expression shown above, the spaces between the nn and the =:, and between the =: and the i., are not required by the interpreter, but have been put there to make the expression more readable. More than one space could have been used with same result, and spaces could have been put between the i. and the 11 without affecting the result. However, putting a space within the =: or the i. would have affected the result just as putting a space within the 11 would have, because the notation uses suffixed dots and colons to extend the ASCII character set, so that in effect = and =. and =: are three different characters. The function f can now be evaluated for the argument nn, and its result displayed, by

   f nn
1 _0.423311 1.10587 10.6608 42.0318 132.705 384.579 1074.64 2955.83 8074.81 21995

which shows a quite distinctive behaviour indeed.

However, the inconveniently small font forced on the printing above of the results of f nn makes it difficult here to appreciate fully the nature of that behaviour, so it is useful to define with scant comment

   show =: ".&(0.1&":)

which names a function that can be used as in

   show f nn
1 _0.4 1.1 10.7 42 132.7 384.6 1074.6 2955.8 8074.8 21995

to force each value to be displayed with at most one decimal place. This is perhaps not so useful here, but will be so later. [The preceding discussion springs from the formatting of the class handout.]

One of the values in the result list is negative. Notice that the notation disambiguates the hyphen by representing the property of negativity by the sign _ so that the hyphen can be kept to stand for the negation and subtraction functions. To investigate how f behaves for negative arguments, the test values can have ten subtracted from them before applying f by keying in

   show f nn-10
31.4 28.3 25.1 22 18.9 15.7 12.6 9.4 6.4 3.5 1

where the values displayed have been rounded to one decimal place by show. To make completely plain what the argument value is for each result value, the laminate function ,: can be used in

   a ,: show f a =: nn-10
 _10   _9   _8 _7   _6   _5   _4  _3  _2  _1 0
31.4 28.3 25.1 22 18.9 15.7 12.6 9.5 6.4 3.5 1

where the test values are temporarily named a so that they can be stacked by ,: on top of the results. Care must be taken in putting together expressions like the one keyed in above. For example, the copula =: gives the name to its left to its predicate, which is everything to its right, just as in English sentences the predicate of a verb is everything to its right. Providing there is a space between the f and the a, then there are two names, and the copula only uses the name next to it. If there’s no space, the name is fa and there is no function f any more. Similarly, the predicate of the laminate function is everything to its right, which is the result of applying f to the values of a.

The same technique can be used to stack the values of nn on the result of f nn by

   nn ,: show f nn
0    1   2    3  4     5     6      7      8      9    10
1 _0.4 1.1 10.7 42 132.7 384.6 1074.6 2955.8 8074.8 21995

which shows quite different behaviour from that for negative argument values.

The most interesting feature here is that the function has two zeroes, one between arguments of 0 and 1, and one between 1 and 2. These zeroes can now be explored one at a time, though the exploration of the second zero is here left as an exercise for the reader.

The First Zero

The simplest zero to explore is the one between 0 and 1, and by

   a ,: show f a=:nn%10
0 0.1 0.2 0.3 0.4 0.5  0.6  0.7  0.8  0.9    1
1 0.8 0.6 0.4 0.2 0.1 _0.1 _0.2 _0.3 _0.4 _0.4

the eleven values between and including 0 and 1 are displayed. Because the conventional divide character ÷ is not usually mapped to a key, the expression nn%10 asks for nn to be divided by ten. Thus, the expression nn%10 gives a list of values between 0 and 1 incrementing by tenths, and from the result of applying f to nn%10 it can easily be seen that the zero lies between argument values of 0.5 0.6, about halfway between.

The limitations of the function show are evident here, but it can easily be redefined as

   show =: ".&(0.2&":)

which then returns a second decimal place to sight in

      a ,: show f a=:nn%10
0  0.1  0.2  0.3  0.4  0.5   0.6   0.7   0.8   0.9     1
1 0.79 0.59 0.41 0.24 0.08 _0.06 _0.19 _0.29 _0.37 _0.42

so that the zero can be predicted to be slightly closer to 0.6 than to 0.5. Not only are fractional values like 0.5 and 0.6 always displayed with a leading zero, but they must also be keyed in with one. Hence the next step in finding the zero, going down to hundredths, is

   a ,: show f a=: 0.5+nn%100
 0.5 0.51 0.52 0.53 0.54 0.55  0.56  0.57  0.58  0.59   0.6
0.08 0.06 0.05 0.03 0.02 0.01 _0.01 _0.02 _0.04 _0.05 _0.06

where the leading zero of the 0.5 of the expression being keyed in is required, not optional. The new display of the results shows that the zero is between argument values 0.55 and 0.56.

The result values are becoming rather too close to zero, and will now need to be scaled up, so in

   a ,: show 1000*f a=: 0.55+nn%1000
0.55 0.551 0.552 0.553 0.554 0.555 0.556 0.557 0.558 0.559  0.56
5.38  3.97  2.56  1.16 _0.24 _1.64 _3.04 _4.44 _5.83 _7.23 _8.62

the results are multiplied by a thousand before being displayed, the * symbol being used to stand for the multiplication function because the × symbol is not usually mapped to a key. In the absence of parentheses, the right argument of a function is the entire expression to the right of the function symbol, so that the * in 1000* takes the result of the application of f as its right argument. By now, the argument values are getting a bit too long to print out comfortably, and the way they correspond should be getting to be clear, so they won’t be shown stacked any longer. The argument of f now being from 0.55 to 0.56, and the zero being located between 0.553 and 0.554, the next narrowing down can be done by

   show 10000*f 0.553+nn%10000
11.6 10.2 8.79 7.39 5.99 4.58 3.18 1.78 0.38 _1.02 _2.42

which now places the zero between arguments 0.5538 and 0.5539. The J interpreter makes it easy enough to bring down the previous line and simply insert the three extra digits needed at each stage of the calculation. But by now the strings of zero digits are becoming a bit hard to keep visual track of, so, using e-notation for the large scaling integer and the small interval size, the next step becomes

   show 1e5*f 0.5538+nn%1e5
3.79 2.39 0.99 _0.42 _1.82 _3.22 _4.62 _6.02 _7.42 _8.83 _10.23

This process of narrowing down has now become somewhat repetitive. Of course, the whole process could be made automatic, but by doing it step by step the succession of numbers gives a better feel for how the function f is behaving. Nevertheless, the repeated part of the expression used just above could relatively easily be encapsulated (by an experienced J user) as in

   g =: ]*f@(+nn&%)

so that the investigation can now proceed as follows.

   show 0.55382 g 1e6
9.86 8.46 7.06 5.66 4.26 2.85 1.45 0.05 _1.35 _2.75 _4.15
   show 0.553827 g 1e7
0.51 _0.89 _2.29 _3.69 _5.09 _6.49 _7.9 _9.3 _10.7 _12.1 _13.5
   show 0.5538270 g 1e8
5.14 3.73 2.33 0.93 _0.47 _1.87 _3.27 _4.68 _6.08 _7.48 _8.88
   show 0.55382703 g 1e9
9.31 7.91 6.51 5.11 3.71 2.31 0.9 _0.5 _1.9 _3.3 _4.7

For some time now the results have been showing a strange uniformity. It might be suspected either that the arithmetic is beginning to give out, or that the variation between successive values has become uniform. This can be checked by taking the result values in pairs and subtracting them. This is done (without explanation here) by

   show 2-/\0.55382703 g 1e9
1.4 1.4 1.4 1.4 1.4 1.4 1.4 1.4 1.4 1.4

which immediately shows the postulated uniformity. Indeed, keying in the expression without the show gives ten values 1.40169 so it is very uniform. This could allow a fairly accurate interpolation by hand of just where the zero is, but, for the purposes of exploration, the estimation process is continued here as before, except that now

   show =: ".&(0.1&":)

because only one decimal place is now needed to be shown.

   show 0.553827036 g 1e10
9 7.6 6.2 4.8 3.4 2 0.6 _0.8 _2.2 _3.6 _5
   show 0.5538270366 g 1e11
6.2 4.8 3.4 2 0.6 _0.8 _2.2 _3.6 _5 _6.4 _7.8
   show 0.55382703664 g 1e12
6.3 4.9 3.5 2.1 0.7 _0.7 _2.1 _3.5 _4.9 _6.3 _7.7
   show 0.553827036644 g 1e13
7.2 5.8 4.4 3 1.6 0.2 _1.2 _2.6 _4 _5.4 _6.8
   show 0.5538270366445 g 1e14
1.9 0.5 _0.9 _2.3 _3.7 _5.1 _6.5 _7.9 _9.3 _10.7 _12.1
   show 0.55382703664451 g 1e15
4.9 3.3 2 0.7 _0.7 _2 _3.3 _4.7 _6.2 _7.5 _8.9
   show 0.553827036644513 g 1e16
6.7 4.4 6.7 2.2 0 0 0 _2.2 _6.7 _2.2 _6.7

The values have become erratic, so much so that doubt must attach to any attempt to add further digits to the value found for the first zero. However, checking with

   f 0.55382703664451 0.553827036644513 0.5538270366445135
4.88498e_15 6.66134e_16 0

suggests that at least the first fifteen decimal places should be reliable, provided the calculation built into the interpreter is reliable to such precision. It is important to realise that the barrier that has been encountered here is one of precision, not range, since

   4*4e32 4e_32
1.6e33 1.6e_31

shows that a much greater range can be satisfactorily used.

Finding The Lowest Value

While the value of the second zero can be as easily found as the first, between the two zeroes all the values of f are negative, it might be presumed. This can be explored by

   show 0.5 g 5
0.4 _0.9 _1.8 _2.3 _2.1 _1.2 0.7 3.6 7.8 13.7 21.6

where the interval between arguments is 0.2 and starting from 0.5. This suggests a minimum value for f of about _2.3%5 when the argument is about 1.1, probably just a bit larger than 1.1.

Before going further, it is convenient to redefine f as its negation, thus

   f =: -&(-o.)

so that the values being looked at will be free of the negative sign, though now what was lowest value will have become the highest. Consequently,

   show 1.1 g 100
45.2 45.3 45.4 45.4 45.5 45.5 45.4 45.4 45.3 45.1 45

suggests that, while the now-highest value of f is 0.455, the argument corresponding to this value is round about 1.14. But this is now estimation, and one decimal place for show is too few.

The next steps in calculation are therefore

   show =: ".&(0.2&":)
   show 1.14 g 1000
454.65 454.66 454.67 454.68 454.68 454.68 454.68 454.67 ...

which verifies the highest value to be about 0.45468 but it is mainly a guess that the argument for that value is about 1.144. Issuing

   show=:".&(0.3&":)

to get three decimal places then allows

   show _45468+10*1.144 g 10000
0.151 0.172 0.191 0.206 0.218 0.226 0.232 0.234 0.234 0.23 0.223

where the _45468+10* reduces the size of numbers displayed and gets an extra decimal place at the same time. The procedure can be continued, thus

   show _4546823+100*1.1447 g 100000
0.447 0.455 0.46 0.461 0.46 0.455 0.447 0.436 0.422 0.405 0.384

which adds an extra digit (a bit uncertainly – is it 2, or 3?) to the value of the argument, and two extra digits to the value of the function. The procedure goes on, here splitting the difference between the 2 and the 3.

   show _454682346+1000*1.144725 g 1000000
0.102 0.116 0.126 0.134 0.138 0.139 0.137 0.132 0.124 0.113 0.098

which confirms 2, and its successor 9, and again adds a couple of digits, 13, maybe even 138, to the value of the function. Now

   show _454682346138+100000*1.144729 g 1e7
0.132 0.395 0.626 0.825 0.994 1.131 1.236 1.311 1.353 1.364 1.344

shows that the last digit of the previous function value should have been 9 rather than 8, but still gives another two function-value digits for the single argument digit. In the next step

   show _45468234613935+1e6*1.1447298 g 1e8
0.273 0.547 0.766 0.984 1.117 1.297 1.344 1.391 1.43 1.477 1.477

leads immediately to

   show _4546823461393647+1e7*1.14472989 g 1e9
10 5 _4 5 1 1 _4 1 1 _4

where everything now blows up at a point where the function value is known to about 16 digits, but the argument value to only 9.

This mismatch between the accuracy of the result and that of its argument is a good illustration of the inherent unreliability of simple computer arithmetic. More extreme examples of mismatch could easily be concocted.

Conclusion

The procedures described in this article show the way, or the style, in which calculations can be carried out interactively using a tool like J. If the calculation tool is mastered, most calculations can be carried out in the same style. The barrier is attaining that mastery.

Indeed the repeated searching for an answer by a kind of trial and error is not characteristic of functional calculation, since mastery will enable functions to be defined which can do the searching automatically. Such virtuosity would not be appropriately demonstrated in an apéritif.

The interactive style of calculation depends on two factors illustrated in this article. The first is simply the ability to key in an expression and have it evaluated ‘on the fly’. The second is the ability to give a name to an expression then use that name from then on as though it were that expression.

This article is only an apéritif to the main meal of getting to grips with a ramified functional calculation tool.

References

  1. Menninger, K. (1958) Zahlwort und Ziffer, Vandenhoeck & Ruprecht, Gottingen, 2nd edition (as Number Words and Number Symbols by MIT Press in 1969).
  2. Thimbleby, H.W. (1995) “A New Calculator and Why it is Necessary”, The Computer Journal, 38(6), 418–433.
  3. Jsoftware: http://www.jsoftware.com
  4. The J Wiki: http://www.jsoftware.com/jwiki

Valid HTML 4.01 Strict

script began 22:06:03
caching off
debug mode off
cache time 3600 sec
indmtime not found in cache
cached index is fresh
recompiling index.xml
index compiled in 0.2231 secs
read index
read issues/index.xml
identified 26 volumes, 101 issues
array (
  'id' => '10011810',
)
regenerated static HTML
article source is 'HTML'
source file encoding is 'UTF-8'
URL: mailto:neville.holmes@utas.edu.au => mailto:Neville.Holmes@utas.edu.au
URL: #ref1 => art10011810#ref1
URL: #ref2 => art10011810#ref2
URL: http://www.jsoftware.com => http://www.jsoftware.com
URL: #ref3 => art10011810#ref3
URL: http://www.jsoftware.com/jwiki => http://www.jsoftware.com/jwiki
URL: #ref4 => art10011810#ref4
URL: ftp://archive.uwaterloo.ca/languages/j => trad/v233/ftp://archive.uwaterloo.ca/languages/j
URL: news:comp.lang.apl => trad/v233/news:comp.lang.apl
URL: http://www.jsoftware.com/ => http://www.jsoftware.com/
URL: http://www.jsoftware.com/ => http://www.jsoftware.com/
URL: http://www.jsoftware.com => http://www.jsoftware.com
URL: http://www.jsoftware.com/jwiki => http://www.jsoftware.com/jwiki
URL: http://validator.w3.org/check?uri=referer => http://validator.w3.org/check?uri=referer
URL: http://www.w3.org/icons/valid-html401 => http://www.w3.org/Icons/valid-html401
completed in 0.2484 secs