Numbers to Words
Vector 20:1, page 25
Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 25
Here is another Zark newsletter extract. As always, don’t hesitate to send in any solutions you may have that you may be inspired to investigate. There may be new more elegant ways of solving an old problem, which may also turn out to be faster. Please don’t be afraid to try and shed "new" light on the "old" problems. Once again, thanks to Jonathan Barman for helping typeset this column.
Utility Corner: Numbers to Words¶
(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 utility function named NUMBERWORDS, which takes a single positive integer as its right argument and returns a character vector that describes the number. For example:
NUMBERWORDS 1234
one thousand two hundred thirty four
NUMBERWORDS 1007
one thousand seven
NUMBERWORDS 0
zero
NUMBERWORDS 1003011
one million three thousand eleven
Given such a function, the task of formatting a cheque amount into words is straightforward:
∇ R←CHECKAMOUNT N;C;D
[1] ⍝ Format a $ amount (e.g. 1234.56)
[2] ⍝ into words (e.g. 'One thousand tow
[3] ⍝ hundred thirty four and 56/100
[4] ⍝ dollars'). Needs NUMBERWORDS.
[5] ⍝
[6] D←⌊N ⍝ Dollars
[7] C←100×N-D ⍝ Cents
[8] D←NUMBERWORDS D ⍝ Dollars to words
[9] ⍝ Capitalize first letter:
[10] D[⎕IO]←'EFNOSTZ'['efnostz'⍳D[⎕IO]]
[11] ⍝ PUt it all together:
[12] R←D,' and',(0⍕C),'/100 dollars'
∇
CHECKAMOUNT 1234.56
One thousand two hundred thirty four and 56/100 dollars
The most important trait we looked for in the NUMBERWORDS functions we received was that it produced the correct result. Speed, program size, and elegance were not considered.
Unfortunately, the concept of "correct" got a bit fuzzy when we started receiving the entries from outside the United States. The British "billion" (a million million) and the American "billion" ( a thousand million) are different. Likewise for Trillion, Quadrillion, and so on.
For the purposes of this discussion, we will use American terminology (what did you expect?). All functions have been converted accordingly.
The solutions below do not attempt to spell numbers beyond 999 quadrillion. This may be due to the fact that such immense numbers are rare indeed, and that the need to spell them is even more rare. Or, it may be due to the fact that no one was quite sure what follows "quadrillion". The most practical explanation is that most APL systems maintain only 16 or 17 significant digits of precision. Since the number 999 quadrillion has 18 digits, the spelling of such a number will be faulty at best.
The first solution, from Jim Weigang, breaks the number into six groups of three digits. Each trio of numbers is then passed to a subfunction (SPELL3), which spells out those three digits (e.g. ‘one hundred twenty three’) and includes the appropriate suffix (e.g. ‘billion’).
∇ R←NUMBERWORDS N;D;⎕IO
[1] ⍝ Returns lowercase character vector
[2] ⍝ spelling (in American English)
[3] ⍝ integer N (0 to 1E18-1).
[4] ⍝ Needs subfn: SPELL2 and globals
[5] ⍝ SPELLTAB1 and SPELLTAB2
[6] ⍝
[7] ⎕IO←1 ⍝ Origin 1 is fine
[8] R←'zero'
[9] →(N=0)/0 ⍝ Special case for zero
[10] D←(18⍴10)⊤N ⍝ Split digits apart
[11] R←D[1 2 3]SPELL3' quadrillion'
[12] R←R,D[4 5 6]SPELL3' trillion'
[13] R←R,D[7 8 9]SPELL3' billion'
[14] R←R,D[10 11 12]SPELL3' million'
[15] R←R,D[13 14 15]SPELL3' thousand'
[16] R←R,D[16 17 18]SPELL3''
[17] ⍝ Discard padding and leading space
[18] R←1↓R~'.'
∇
∇ R←D SPELL3 S;A;B;⎕IO
[1] ⍝ Spells the three digits D and adds
[2] ⍝ the ' xillions' suffix S.
[3] ⍝
[4] ⎕IO←1
[5] R←'' ⍝ Empty result for zero
[6] →(D∧.=0)/0
[7] ⍝ Branch if less than 100
[8] →(D[1]=0)/L1
[9] ⍝ Spell the first digit
[10] R←SPELLTAB1[1+D[1];],' hundred'
[11] ⍝ Branch if 20 or more
[12] L1:→(D[2]≥2)/L2
[13] ⍝ Else pack 0 to 19 and spell it:
[14] R←R,SPELLTAB1[1+10⊥1↓D;]
[15] →L3
[16] L2:A←SPELLTAB2[D[2];] ⍝ E.g. forty
[17] B←SPELLTAB1[1+D[3];] ⍝ E.g. seven
[18] R←R,A,B
[19] L3:R←R,S ⍝ E.g. million
∇
Here are the globals needed by SPELL3:
SPELLTAB1
..........
one......
two......
three....
four.....
five.....
six......
seven....
eight....
nine.....
ten......
eleven...
twelve...
thirteen.
fourteen.
fifteen..
sixteen..
seventeen
eighteen.
nineteen.
SPELLTAB2
........
twenty.
thirty.
forty..
fifty..
sixty..
seventy
eighty.
ninety.
Notice the use of period (.). On line [18] of NUMBERWORDS, the expression R←1↓R~'.' generates the desired spacing (one space between each word) by excluding the periods but leaving the one space that precedes each word in SPELLTAB1 and SPELLTAB2 (except for the very first space).
The second solution, from Ray Powell, breaks the number into only as many digits as it contains (using 10⍟N), and then works with the digits in reverse order of significance. The words are then reversed at the end. Ray’s solution uses nested arrays (submitted in Sharp APL notation, but displayed here in APL2/APL*PLUS notation) to simplify working with words of different lengths.
∇ R←NUMBERWORDS N;A;C;I;S;SFX;T;TENS;UNITS;⎕IO
[1] ⍝ Returns lowercase character vector
[2] ⍝ spelling (in American English)
[3] ⍝ integer N (0 to 1E18-1)
[4] ⍝
[5] ⎕IO←1 ⍝ Origin 1 is fine
[6] R←'zero'
[7] →(N=0)/0 ⍝ Exit if zero
[8] ⍝ Nested vectors of possible values:
[9] UNITS←'one' 'two' 'three' 'four' 'five' 'six' 'seven' 'eight' 'nine' 'ten' 'eleven' 'twelve' 'thirteen' 'fourteen' 'fifteen' 'sixteen' 'seventeen' 'eighteen' 'nineteen'
[10] TENS←'ten' 'twenty' 'thirty' 'forty' 'fifty' 'sixty' 'seventy' 'eighty' 'ninety'
[11] SFX←'' 'thousand' 'million' 'billion' 'trillion' 'quadrillion'
[12] ⍝ Break number into digits reversed:
[13] S←⍴A←⌽((1+⌊10⍟N)⍴10)⊤N
[14] ⍝
[15] ⍝ R: nested vec of words in result.
[16] ⍝ C: nested vec of last 3 digit grp
[17] R←C←⍳0
[18] I←0 ⍝ Loop by digit
[19] LOOP:→(S<I←I+1)/END
[20] ⍝ Units, tens, or hundreds?
[21] →(1 2 0=3|I)/LU,LT,LH
[22] ⍝
[23] ⍝ C contains the description for the
[24] ⍝ previous group of three. Save it.
[25] LU:R←R,C
[26] C←⍳0
[27] ⍝ Branch unless this group is all 0:
[28] →(0∨.≠3↑(I-1)↓A)/L1
[29] I←I+2 ⍝ Junp to the next group
[30] →LOOP
[31] ⍝ Branch if not SFX description:
[32] L1:→(1=T←⌈I÷3)/L2
[33] C←SFX[T] ⍝ Else grab it
[34] ⍝ Ignore if this digit is zero:
[35] L2:→(A[I]=0)/LOOP
[36] ⍝ Branch if next digit gives 20+
[37] ⍝ (or if no more digits)
[38] →(S=I)/L3
[39] →(19<T←10⊥A[I+1 0])/L3
[40] ⍝ Else use 1-19 table and increment:
[41] C←C,UNITS[T]
[42] I←I+1
[43] →LOOP
[44] ⍝ Use 1=9 table and don_t increment:
[45] L3:C←C,UNITS[A[I]]
[46] →LOOP
[47] ⍝
[48] ⍝ Ignore if this digit is zero:
[49] LT:→(A[I]=0)/LOOP
[50] C←C,TENS[A[I]]
[51] →LOOP
[52] ⍝
[53] ⍝ Ignore if this digit is zero:
[54] LH:→(A[I]=0)/LOOP
[55] C←C,⊂(A[I]⊃UNITS),' hundred'
[56] →LOOP
[57] ⍝
[58] ⍝ Stick blanks between words:
[59] END:R←1↓⊃,/' ',¨⌽R,C
∇
The third solution, from Ram Biyani, recurses, i.e. calls itself. The function is written to handle 0, or 1 to 19, or 20 to 99. Beyond 99, the function breaks the number into seven groups of digits (quadrillions, trillions, billion, millions, thousands, hundreds, and 0 to 99), and calls itself once for each of these new numbers, applying the appropriate suffix to each, and retaining only those that are not zero. Again, nested arrays are used.
∇ R←NUMBERWORDS N;SFX;TENS;UNITS;V;⎕IO
[1] ⍝ Returns lowercase character vector
[2] ⍝ spelling (in American English)
[3] ⍝ integer N (0 to 1E18-1). Recurses.
[4] ⍝
[5] R←'zero'
[6] →(N=0)/0 ⍝ Exit if zero
[7] →(N≥100)/BIG ⍝ Branch if 100+
[8] ⍝
[9] UNITS←'' 'one' 'two' 'three' 'four' 'five' 'six' 'seven' 'eight' 'nine' 'ten' 'eleven' 'twelve' 'thirteen' 'fourteen' 'fifteen' 'sixteen' 'seventeen' 'eighteen' 'nineteen'
[10] ⍝ Brance unless 1 to 19:
[11] →(N≥20)/MEDIUM
[12] R←(N+⎕IO)⊃UNITS ⍝ Pick it out
[13] →0
[14] ⍝
[15] ⍝ Tens (20 to 99)
[16] MEDIUM:TENS←'twen' 'thir' 'for' 'fif' 'six' 'seven' 'eigh' 'nine',¨⊂'ty'
[17] ⍝ Break apart tens and untis:
[18] V←10 10⊤N
[19] R←1↓⊃,/(V≠0)/' ',¨⎕IO⊃¨(V-2 0)↓¨TENS UNITS
[20] →0
[21] ⍝ Recurse to translate numbers ≥100:
[22] BIG:SFX←' quadrillion' ' trillion' ' billion' ' million' ' thousand' ' hundred' ''
[23] ⍝ Break apart number:
[24] V←(0,((¯3+⍴SFX)⍴1000),10 100)⊤N
[25] R←1↓⊃,/(V≠0)/' ',¨(NUMBERWORDS¨V),¨SFX
∇
The fourth solution, from Brad Buck, also recurses. The function is written to handle 0 to 19 without recursing. For any number beyond that, it recurses. This function looks at the magnitude of the number (via ⌊1000⍟N) to determine the largest suffix required, and recurses only so many times as it needs to. Nested arrays are not used.
∇ R←NUMBERWORDS N;F;M;P;T;⎕IO
[1] ⍝ Returns lowercase character vector
[2] ⍝ spelling (in American English)
[3] ⍝ integer N (0 to 1E18-1). Recurses.
[4] ⍝
[5] ⎕IO←1 ⍝ Origin 1 works best
[6] ⍝ Zero, Units, Tens, Hundreds:
[7] →(N≤0 19 99 999)/LZ,LU,LT,LH
[8] ⍝
[9] ⍝ Else 1000 or more:
[10] P←⌊1000⍟N ⍝ 1=1E3→1E6, 2=1E6→1E9...
[11] T←5 11⍴'thousand million billion trillion quadrillion'
[12] T←T[P;]~' '
[13] R←(NUMBERWORDS M←⌊N÷F←1000*P),' ',T
[14] →MORE
[15] ⍝
[16] ⍝ Zero:
[17] LZ:R←'zero'
[18] →0
[19] ⍝
[20] ⍝ Units (1 to 19):
[21] LU:T←19 9⍴'one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeeneighteen nineteen '
[22] R←T[N;]~' '
[23] →0
[24] ⍝
[25] ⍝ Tens (20 to 99):
[26] LT:T←10 7⍴'ten twenty thirty forty fifty sixty seventyeighty ninety '
[27] R←T[M←⌊N÷F←10;]~' '
[28] →MORE
[29] ⍝
[30] ⍝ Hundreds (100 to 999):
[31] LH:R←NUMBERWORDS M←⌊N÷F←100
[32] R←R,' hundred'
[33] ⍝
[34] ⍝ Reduce by amount formatted so far:
[35] MORE:N←N-M×F
[36] →(N=0)/0 ⍝ Exit if nothing left
[37] ⍝ Else include the rest
[38] R←R,' ',NUMBERWORDS N
∇
Back in 1976, I was working for Scientific Time Sharing Corporation. I remember a broadcast request from Jack Becker, who wrote the company’s internal accounting systems, for exactly such a function. I came up with what I thought was a very clever recursive solution:
∇ R←WORDS X;A;B;C;D;H;O;P;Q;S;T;⎕CT;⎕IO
[1] ⍝ TRANSLATE AN INTEGER INTO WORDS
[2] ⍝ REQUIRES: -
[3] ⍝ REX SWAIN 11 MAY 1976; ENHANCED SLIGHTLY 30 DECEMBER 1981
[4] ⎕CT←0 ⋄ ⎕IO←1
[5] S←+/⎕SI[;⍳6]∧.='WORDS[' ⍝ RECURSION DEPTH
[6] →(0≠X)⍴a ⋄ R←(1=S)/'ZERO' ⋄ →0
[7] a:A← 10 6 ⍴'ZERO ONE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE '
[8] B←'TEN ELEVEN TWELVE THIRTEEN FOURTEEN FIFTEEN '
[9] B← 10 10 ⍴B,'SIXTEEN SEVENTEEN EIGHTEEN NINETEEN '
[10] C←' TWENTY THIRTY FORTY FIFTY SIXTY '
[11] C← 10 8 ⍴C,'SEVENTY EIGHTY NINETY '
[12] D← 5 9 ⍴' THOUSAND MILLION BILLION TRILLION '
[13] P←1+Q← 10 10 10 ⊤X ⍝ LAST THREE DIGITS
[14] H←(×Q[1])/A[P[1];],'HUNDRED ' ⍝ HUNDREDS
[15] T←(2≤Q[2])/C[P[2];] ⍝ TENS
[16] O←((1=Q[2])/B[P[3];]),(1 0 ∧.≠1↓Q)/A[P[3];] ⍝ TEENS OR ONES
[17] R←(WORDS⌊X÷1000),H,T,O,(0≠1000|X)/D[S;] ⍝ RECURSE AND ASSEMBLE
[18] →(1≠S)⍴0 ⋄ B←R≠' ' ⋄ R←(B∨1⌽B><\B)/R ⍝ DELETE EXTRANEOUS BLANKS
∇
But a couple of days later another STSC’er named Bob DeCloss submitted a different solution:
∇ R←WORDS2 N;A;B;C;D;a;b;c;d
[1] ⍝ CONVERTS INTEGER ARRAY INTO WORDS
[2] ⍝ REQUIRES: -
[3] ⍝ BOB DECLOSS, CIRCA 1976
[4] a← 10 6 ⍴'////////ONE //TWO THREE /FOUR /FIVE //SIX SEVEN EIGHT /NINE '
[5] b←'///////////////////////TWENTY ///THIRTY ////FORTY ////FIFTY ////'
[6] b←b,'SIXTY //SEVENTY ///EIGHTY ///NINETY //////TEN ///ELEVEN ///'
[7] b←b,'TWELVE /THIRTEEN /FOURTEEN //FIFTEEN //SIXTEEN SEVENTEEN /'
[8] b← 20 10 ⍴b,'EIGHTEEN /NINETEEN '
[9] c←'////////////////ONE HUNDRED //TWO HUNDRED THREE HUNDRED /'
[10] c←c,'FOUR HUNDRED /FIVE HUNDRED //SIX HUNDRED SEVEN HUNDRED '
[11] c← 10 14 ⍴c,'EIGHT HUNDRED /NINE HUNDRED '
[12] d← 4 11 ⍴'/////////////THOUSAND ///MILLION ///BILLION '
[13] C←3×D←⌈(⍴⍕⌈/N←,N)÷3 ⋄ B←''⍴⍴A←((D×⍴N),3)⍴⍉(C⍴10)⊤N
[14] C←(A[;2]=1)/⍳B ⋄ A[C;2]←A[C;3]+10×A[C;2]
[15] A[C;3]←0 ⋄ A←A+1
[16] R←,c[A[;1];],b[A[;2];],a[A[;3];],d[1⌈(∨/1<A)×⌽B⍴⍳D;]
[17] R[(R='/')/⍳⍴R]←⎕TCDEL ⋄ R←((⍴N),D×41)⍴R
∇
No recursion. No looping. No branching. And it does more than one number at once. Wow.
Rex Swain ; Independent Consultant
Web: http://www.rexswain.com
Limbering Up: Comparing Functions¶
(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.)
When maintaining a large APL application, you will occasionally find yourself face-to-face with two copies of the same function. Yet the copies are not the same. Perhaps two different programmers enhanced the same function in different ways. You would like to know how the functions are different.
The first objective is to bring the two functions together so they can be compared. Yet, if you copy both functions into the workspace, only one will survive (since they have the same name!) To compare the functions you must convert them to character array representations via ⎕CR (canonical representation) or ⎕VR (visual representation). For example:
Now you have two character matrices that "look like" the function. Your task is to write a function CRCOMPARE (or VRCOMPARE if you are using ⎕VR) that returns/displays the differences between the functions, like so:
CR1 CRCOMPARE CR2
[1] A+B+C
[2] D←3
---- replaced by ----
[1] A+B+C ⋄ D←3
[5] A←B←0 0 ⍴0
---- added ----
[6] A←0 0 ⍴0
[7] B←0 0 ⍴0
---- deleted ----
[9] Z←0.0×D
---- replaced by ----
[7] Z←D÷100
If using ⎕VR, make sure you ignore the line numbers, which are part of the visual representation, when comparing the old and new functions. Otherwise, if a line is inserted between [5] and [6], each line in the new function from [7] up will appear different because of its new line number. If using ⎕CR, make sure you display the line numbers with the modified lines of code.
Send your solution to: Vector Production, Brook House, Gilling East, York YO62 4JJ, UK.
The notable functions and their authors’ names will be printed in the next issue of Vector. Good luck and happy limbering.
Reprinted with kind permission from Zark APL Tutor News, a quarterly publication of Zark Incorprated, 23 Ketchbrook Lane, Ellington, CT06029, USA.