Call Again and Again and Again…
October 5, 2011 Paul Tuohy
Note: The code accompanying this article is available for download here.
In programming terms, recursion is the process whereby a function may call itself. Traditionally, this is something we are not used to in RPG. Programs and subroutines cannot call themselves. Or if you did somehow manage it (and you could), you would get unpredictable results.
But the introduction of subprocedures opened up the possibility of using recursion because subprocedures can call themselves.
It is usually very difficult to come up with practical examples of using recursion, and most of those examples are usually very specific to an application. For example, a bill of materials process or producing an organizational chart. But I recently came across an old “problem” to which recursion provided an elegant solution: converting numbers to words.
In this article, I will discuss the mechanics of coding recursive subprocedures and give a practical example of where you might use one.
But First, A Word of Warning
As will all powerful programming concepts, you need to be very careful when using recursive calls. If you do not have some logic that breaks the recursive call sequence, you will end up with a never-ending process. As with a never-ending loop, you will be the “dog chasing its tail.”
The first question you always ask when using recursion is: “How do I get it to stop?”
Understanding the Problem
At first glance, the problem seems simple enough. Take a number, segregate it into millions/thousands/hundreds/tens/singles, and put each segregation in words (your challenge will be to include billions). The real problem comes when you go to put a segregation into words, because each segregation may be further segregated. For example, the number 12,345,678,901.23 would be translated to twelve thousand, three hundred and forty-five million, six hundred and seventy-eight thousand, nine hundred and one dollars, and 23 cents. How many millions?
The process is as follows:
Let’s look at a program that demonstrates the process. The following code shows the mainline of a program that prompts for a number, calls the currencyToWords() subprocedure, and displays the returned value.
d forNumber s 11s 2 d ds d words 200a varying d showWords1 50a overLay(words : 3) d showWords2 50a overLay(words : *next) d showWords3 50a overLay(words : *next) d showWords4 50a overLay(words : *next) /free dsply 'Enter number: ' ' ' forNumber; words = currencyToWords(forNumber); dsply ShowWords1; dsply ShowWords2; dsply ShowWords3; dsply ShowWords4; *inLr = *on; /end-Free
Now let’s look at some code that shows the currencyToWords() subprocedure that accepts a complete number (including decimals) and two optional parameters to identify the names of the currency and the decimal units (they default to dollars and cents). The procedure extracts the decimal portion of the number, calls the numberToWords() subprocedure to convert the integer portion of requested number into words, and returns a complete description of the requested number as words.
P currencyToWords... P B d PI 200a varying d number 13s 2 const d currency 20a varying const d options(*noPass: *trim) d decimals 20a varying const d options(*noPass: *trim) d forCurrency s 20a varying inz('Dollars') d forDecimals s 20a varying inz('cents') d cents s 2s 0 /free if (%parms() > 1); forCurrency = currency; endIf; if (%parms() > 2); forDecimals = decimals; endIf; cents = %int((number - %int(number)) * 100); return numberToWords(%int(number)) + ' ' + forCurrency + ' and ' + %editC(cents: 'X') + ' ' + forDecimals; /end-Free P E
Finally, the next piece of code shows the numberToWords() subprocedure, which returns a text description of a requested integer.
The D specs contain the definition of:
The process in the subprocedure is as follows:
The recursive calls cease when all segregated portions are zero, which has to happen since the number is being constantly segregated.
P numberToWords... P B d PI 200a varying d numberIn 9s 0 const d number s 10i 0 d mega s 10i 0 d kilo s 10i 0 d hundreds s 10i 0 d tens s 10i 0 d singles s 10i 0 d result s 200a varying d forOneWords ds d 9a varying inz('One') d 9a varying inz('Two') d 9a varying inz('Three') d 9a varying inz('Four') d 9a varying inz('Five') d 9a varying inz('Six') d 9a varying inz('Seven') d 9a varying inz('Eight') d 9a varying inz('Nine') d 9a varying inz('Ten') d 9a varying inz('Eleven') d 9a varying inz('Twelve') d 9a varying inz('Thirteen') d 9a varying inz('Fourteen') d 9a varying inz('Fifteen') d 9a varying inz('Sixteen') d 9a varying inz('Seventeen') d 9a varying inz('Eighteen') d 9a varying inz('Nineteen') d oneWords 9a varying dim(19) d overlay(forOneWords) d forTenWords ds d 7a varying inz('') d 7a varying inz('Twenty') d 7a varying inz('Thirty') d 7a varying inz('Forty') d 7a varying inz('Fifty') d 7a varying inz('Sixty') d 7a varying inz('Seventy') d 7a varying inz('Eighty') d 7a varying inz('Ninety') d tenWords 7a varying dim(9) d overlay(forTenWords) /free number = numberIn; mega = %int(number / 1000000); number -= mega * 1000000; kilo = %int(number / 1000); number -= kilo * 1000; hundreds = %int(number / 100); number -= hundreds * 100; tens = %int(number / 10); singles = number - (tens * 10); result = ''; if (mega > 0) ; result += numberToWords(mega) + ' Million '; endIf; if (kilo > 0) ; result += numberToWords(kilo) + ' Thousand '; endIf; if (hundreds > 0) ; result += numberToWords(hundreds) + ' Hundred '; endIf; if (tens > 0 or singles > 0); if (result <> ''); result += 'and '; endIf; if (tens < 2); result += oneWords(tens * 10 + singles); else; result += tenWords(tens); if (singles <> 0); result += ' ' + oneWords(singles); endIf; endIf; endIf; if (result = ''); result = 'zero'; endIf; return result; /end-Free P E
There You Have It
Although not something you might be using on a daily basis, recursive calls are a powerful tool to have in your toolbox. Just remember, always know how to break the recursive loop.
Paul Tuohy is CEO of ComCon, an iSeries consulting company, and is one of the co-founders of System i Developer, which hosts the RPG & DB2 Summit conferences. He is an award-winning speaker who also speaks regularly at COMMON conferences, and is the author of “Re-engineering RPG Legacy Applications,” “The Programmers Guide to iSeries Navigator,” and the self-study course called “iSeries Navigator for Programmers.” Send your questions or comments for Paul to Ted Holt via the IT Jungle Contact page.