﻿ Vector, the Journal of the British APL Association

## Volumes

British APL Association

Archive articles posted online on request: ask the archivist.

Volume 21, No.4

## Utility Corner: To E Or Not To E

(The purpose of this column is to make you more productive by introducing you to utility functions. Think of utility functions as APL functions that have names instead of symbols. By expanding your function vocabulary, you'll be able to write APL code that’s more concise, more efficient, and more readable.)

In last issue’s Limbering Up column, you were asked to define a monadic utility function ENOFF (Exponential Notation OFF) that behaves exactly like monadic except it never returns it s formatted numbers in exponential notation.

APL has a tendency to use exponential notation when the numbers it’s displaying are very large or very small. For example:

```      .0000012345
0.0000012345
.00000012345
1.2345E¯7
1234500000
1234500000
12345000000
1.2345E10
```

While exponential notation does succeed at expressing number in fewer characters, it does not necessarily improve the clarity of the numbers being displayed.

Here’s an example (from Jim Weigang) that shows a display with and without exponential notation

```      M←(10*¯4+⍳8)∘.×1.234 1.235 0 ¯1.235
⎕PP←3
M
1.23E¯3 1.24E¯3 0 ¯1.24E¯3
1.23E¯2 1.24E¯2 0 ¯1.24E¯2
1.23E¯1 1.24E¯1 0 ¯1.24E¯1
1.23E0  1.24E0  0 ¯1.24E0
1.23E1  1.24E1  0 ¯1.24E1
1.23E2  1.24E2  0 ¯1.24E2
1.23E3  1.24E3  0 ¯1.24E3
1.23E4  1.24E4  0 ¯1.24E4

ENOFF M
0.00123     0.00124 0     ¯0.00124
0.0123      0.0124  0     ¯0.0124
0.123       0.124   0     ¯0.124
1.23        1.24    0     ¯1.24
12.3        12.4     0    ¯12.4
123         124       0   ¯124
1234        1235       0  ¯1235
12340       12350       0 ¯12350
```

As you can see, the ENOFF function can make a numeric display more meaningful.

The ENOFF functions we received generally take one of two approaches. In the first, logarithms () are used to determine the relative magnitude of the numbers. From the magnitude and the current setting of ⎕PP (Print Precision), you can determine the appropriate left argument of dyadic that will give the desired result.

The following function illustrates the approach for a scalar (single number) argument.

```
∇ R←ENOFF N;D;G;W
   ⍝ Returns ⍕N for scalar N, but never
   ⍝ returns exponential notation.
   ⍝
    G←1+⌊10⍟|N+N=0
   ⍝ |G is the number of digits to the
   ⍝ left of the decimal (if G>0) or
   ⍝ the number of consecutive zeros to
   ⍝ the right of the decimal (G≤0).
   ⍝
  ⍝ Digits to right of decimal
   D←0⌈⎕PP-G
  ⍝
  ⍝ Required field widths...
   W←D+1⌈G ⍝ Total no. digits
  ⍝ Negative sign and decimal point:
   W←W+(N<0)+D≠0
  ⍝
   R←(W,D)⍕N ⍝ Format it
   →D↓0 ⍝ Done if no decimal point
  ⍝
  ⍝ Delete trailing 0s:
   G←+/^\'0'=⌽R
   R←(-G+G=D)↓R ⍝ And solitary point
∇```

For ⎕PP←3, here are some intermediate values of this function’s local variables. The values are shown for four different settings of the right argument N:

```      N   0.001234  12.34  ¯0.01234  0.1

   G      ¯2       2      ¯1       0
  D       5       1       4       3
  W       6       3       5       4
  W       7       4       7       5
  R   0.00123   12.3   ¯0.0123  0.100
  G       0       0       0       2
  R   0.00123   12.3   ¯0.0123  0.1
```

Notice that lines  and  remove any trailing zeros to the right of the decimal point. They remove the decimal point too if the result is an integer.

The submission from Jim Weigang utilises this approach, and has additional logic to handle numeric arrays of any dimensions:

```     ∇ R←{P}ENOFF N;B;D;E;F;G;S;T;V;W;X;Z;⎕IO
   ⍝ Behaves like monadic ⍕ but never
   ⍝ returns exponential notation.
   ⍝ Like ⍕, it is sensitive to ⎕PP.
   ⍝ Optional left argument is ⎕PP
   ⍝ surrogate
   ⍝
    ⎕IO←1
   ⍝ Set P from ⎕PP if no left arg:
    ⍎(0=⎕NC'P')/'P←⎕PP'
  ⍝ Empty result for empty arg:
   →(0∊S←⍴N)↓L1
   R←S⍴''
   →0
  ⍝ Make numbers a matrix:
  L1:N←((×/¯1↓S),¯1↑1,S)⍴N
   G←1+⌊10⍟|N+N=0
  ⍝ MG is the number of digits to the
  ⍝ left of the decimal (if G>0) or
  ⍝ the number of consecutive zeros to
  ⍝ the right of the decimal (G≤0)
  ⍝
  ⍝ Compute the appropriate Width and
  ⍝ Digits format for each number:
   D←0⌈P-G ⍝ Digits to rt. of decimal
  ⍝ Required field widths...
  ⍝ One blank to left plus all digits:
   W←1+D+1⌈G
  ⍝ Negative sign and decimal point:
   W←W+(N<0)+D≠0
  ⍝ Shift needed to alogn decimals:
   T←((⍴D)⍴⌈⌿D)-D
  ⍝ One more if decimal absent in
  ⍝ column that has some decimals:
   T←T+(D=0)^(⍴D)⍴∨⌿D>0
  ⍝ Increase width for shift:
   W←W+T
  ⍝ Make each col have uniform width:
   W←(⍴W)⍴⌈⌿W
  ⍝ Formatted Matrix shape
   G←(1↑⍴N),+/W[1;]
  ⍝ Adjust field widths to slide the
  ⍝ decimal points into alignment:
   W←W-T-(⍴T)⍴¯1↓0,,T
  ⍝
  ⍝ Format each number:
   R←(,W,[2.5]D)⍕,N
  ⍝ Make it a matrix
   R←G⍴(×/G)↑R
  ⍝
  ⍝ Remove trailing zeros to the
  ⍝ right of the decimal, and delete
  ⍝ excess blank columns:
   V←,⌽R ⍝ Work with reversed vector
  ⍝ A trick to avoid zero partitions:
   V←'. ',V
  ⍝ 1s mark first char of each number:
   F←B>¯1↓0,B←V≠' '
  ⍝ Ignore those without a decimal:
  ⍝ F←F\F pORRED V='.'
   X←V='.'
   Z←(X∨F)/F
   F←F\(Z/1⌽Z)≤F/X
  ⍝ 1s mark leading (nee trailing) 0s:
  ⍝ T←F pANDSCAN V='0'
   X←V='0'
   Z←~(T←X≤F)/X
   T←~\T\Z≠¯1↓0,Z
  ⍝ Undo the trick:
   T←2↓T
   V←2↓V
  ⍝ 1s mark char just past each group
  ⍝ of 0s:
   D←T<¯1↓0,T
   T←T∨D\'.'=D/V
   T←T∨V=' ' ⍝ Delete blanks, too
   D←G⍴T←~T ⍝ 0s mark stuff to delete
  ⍝ When expanding, put 1 blank
  ⍝ between cols:
   E←(B∨¯1↓0,B←∨⌿D)/D
  ⍝ Delete 0s and blanks, insert
  ⍝ minimum blanks:
   V←(⍴E)⍴(,E)\T/V
  ⍝ Strip final blank, undo reversal:
   V←⌽0 ¯1↓V
  ⍝
   R←((¯1↓S),¯1↑⍴V)⍴V
∇
```

In the second approach, monadic format is immediately applied to the numeric argument, in the hopes that APL may not have chosen to foil us with exponential notation. If there is no exponential notation (no E’s), we’re done. If there is exponential notation, only those numbers using it need to be reworked. Even then, some useful information can be gleaned from the exponential format of the number.

Again, the following function illustrates the approach for a scalar (single number) argument.

```     ∇ R←ENOFF N;B;D;I;P;⎕IO
   ⍝ Returns ⍕N for scalar N, but never
   ⍝ returns exponential notation.
   ⍝
    →('E'∊R←⍕N)↓⎕IO←0
    I←R⍳'E'
    P←⍎(I+1)↓R ⍝ Power
    B←I↑R ⍝ Base
   ⍝ No. decimal places in base:
    D←(⍴B)-1+(+/'.¯'∊B)+'0'=¯1↑B
   R←1↓(0⌈D-P)⍕N
∇```

Here are some intermediate values of the local variables when ŒPP"3, using the same settings of the right argument N illustrated above:

```      N  0.001234  12.34   ¯0.01234   0.1

   R  1.23E¯3   1.23E1  ¯1.23E¯2  1.0E¯1
   I     4         4       5        3
   P    ¯3         1      ¯2       ¯1
   B   1.23      1.23   ¯1.23      1.0
   D     2         2       2        0
  R  0.00123    12.3  ¯0.0123    0.1
```

Notice that the base and power portions of the number (e.g. 1.23 and ¯3 from 1.23E¯3) are analysed to determine the appropriate left argument to dyadic . In this simple function, dyadic is called with a single number left argument, which always returns a leading blank in its result. In the function below, which was submitted by Bruce Hitchcock, the more typical pairs-of-numbers left argument is used.

```       ∇ R←ENOFF N;B;C;CD;CM;D;E;EX;I;L;M;P;S;SX;T;U;W;X;Y;⎕IO
   ⍝ Behaves like monadic ⍕ but never
   ⍝ returns exponential notation.
   ⍝ Like ⍕, it is sensitive to ⎕PP
   ⍝
    →('E'∊R←⍕N)↓0
    ⎕IO←1 ⍝ Origin 1 is fine
    R←,R ⍝ Make it a vector
    T←R≠' ' ⍝ Flag non blanks
   ⍝ Inds of starts of no.s:
   S←(T>¯1↓0,T)/⍳⍴T
   E←(T>1↓T,0)/⍳⍴T ⍝ ... and ends
   Y←⍴L←1+E-S ⍝ Lengths of the no.s
  ⍝ Which ones contain E_s:
   T←+\X←R='E'
   I←(T[E]≠T[S])/⍳Y
  ⍝ Numbers to reformat:
   M←(,N)[I]
  ⍝ Indices of the E_s:
   X←X/⍳⍴X
  ⍝ Starts/ends of these no.s:
   SX←S[I]
   EX←E[I]
  ⍝ Power portion of no. (e.g. ¯5 for
  ⍝ 1.234E¯5
   U←(EX-X)+EX<⍴R ⍝ Lengths
  ⍝ U←∊X+⍳¨U:
   U←U/X-¯1↓0,+\U
   U←U+⍳⍴U
   P←⎕FI R[U] ⍝ Use ⎕FI or ⍎
  ⍝ No. decimal places in base (e.g.
  ⍝ 3 for ¯1.234E5 or 0 for ¯1.0E5):
   T←0,+\R∊'.¯'
   D←X-SX+1+(T[X]-T[SX])+R[X-1]='0'
  ⍝ No. decimal places to show:
   D←0⌈D-P
  ⍝ Total width of number:
   W←(R[SX]='¯')+(1-P+1)+(D≠0)+D
  ⍝ Reformat these numbers
   M←(,W,[1.5]D)⍕M
  ⍝ Make room to insert them:
   T←(⍴R)⍴1
   U←EX-SX ⍝ Lengths
  ⍝ U←∊SX+⍳¨U:
   U←U/SX-¯1↓0,+\U
   U←U+⍳⍴U
   T[U]←0
   T[SX]←W
   R←T/R
  ⍝ Update lengths, starts, ends:
   L[I]←W
   E←(+\T)[E]
   S←1+E-L
   SX←S[I]
   EX←E[I]
   U←(EX-X)+EX<⍴R ⍝ Lengths
  ⍝ W←∊(SX-1)+⍳¨W
   W←W/(SX-1)-¯1↓0,+\W
   W←W+⍳⍴W
   R[W]←M ⍝ Insert the new numbers
   →(1≥⍴⍴N)⍴0 ⍝ Exit if vector result
  ⍝
  ⍝ Need at least one blank before and
  ⍝ after each no. (before is fine):
   R←R,' '
  ⍝ Find index of decimal point within
  ⍝ each no. (or 1 beyound end):
   T←+\X←R='.'
   T←T[E]=T[S]
   P←(~T)\X/⍳⍴X
   P[T/⍳Y]←1+T/E
  ⍝ No. digits to left of point:
   M←P-S
  ⍝ ... and to right, including point:
   D←0⌈E-P
   D←D+×D
  ⍝ Largest of each by column:
   T←Y÷C←¯1↑⍴N
   CM←1+⌈⌿(T,C)⍴M ⍝ Plus leading blank
   CD←⌈⌿(T,C)⍴D
  ⍝ Replication vector for expanding:
   T←R≠' '
   T[S-1]←(Y⍴CM)-M
   T[E+1]←T[E+1]+(Y⍴CD)-D
   R←((¯1↓⍴N),+/CM+CD)⍴T/R
∇  ```

From timings we performed on the two functions above, the second approach seems to be quicker, running in 50% to 75% the time required by the first approach. Since these timings depend on the rank and nature of the numbers, we recommend you perform your own timings if speed is critical to your application.

Ed: There was a small problem verifying that the last two functions (which use monadic format) worked correctly, as the formatting rules in different flavours of APL vary slightly. The original article was evidently developed with APL*PLUS, but I used Dyalog APL 8.2 to reproduce and test the functions. There is no leading space for the first column of a formatted matrix in Dyalog APL, so line in the fourth ENOFF function had to be amended to R←,' ',R. Also, Dyalog is much less willing to display exponential format for numbers in a simple vector, so extreme measures were necessary to reproduce the intermediate variables when running the simplified (scalar) example function. I never managed to get 0.1 to display as 1.0E¯1 and had to put up with 1E¯1 or 1.00E¯1 instead.

## Limbering Up: Accumulations

(The purpose of this column is to work some flab off your APL midsection. Like muscles, your APL skills can atrophy if not exercised with adequate frequency and variety. This column presents a task for you to perform. Set aside a few minutes from your busy schedule and work the task. Mail in your solution and stay tuned for the results.)

A classic: suppose you have two numeric vectors whose elements are in one-to-one correspondence. The first ( ACCT) is a vector of account numbers; the second ( AMT) is a vector of dollar amounts. The account numbers in ACCT are not distinct; they repeat. What are the distinct account numbers? How many times does each account number occur? What is the sum of the numbers in AMT for each distinct account number? What is the percent of each number in AMT relative to the total for its account?

The efficient APL algorithms for these problems are well known and will be discussed in the next issue. Your task is to design the syntax of one or more utility functions that make the solutions to such problems convenient and intuitive.

Vector Production
Brook House
Gilling East
York YO62 4JJ
UK
email gill@apl385.com@compuserve.com

The notable functions and their authors’ names will be printed in the next issue of Vector. Good luck and happy limbering.

```script began 4:22:27
caching off
debug mode off
cache time 3600 sec
cached index is fresh
recompiling index.xml
index compiled in 0.1772 secs