Guru Classic: Call Again And Again And Again . . .
January 16, 2019 Paul Tuohy
Author’s Note: This article was originally published in October 2011 and recently came to mind when I had a discussion with a programmer bemoaning the fact that he could not (so he thought) have a recursive process in RPG. The content of the article has been updated for free form RPG and some of the coding enhancements that have been introduced, into RPG, since 2009.
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.
This story contains code, which you can download here.
But First, A Word Of Warning
As with 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:
- Segregate a number into millions, thousands, hundreds, tens and singles
- If a segregation is greater than zero, then segregate the segregation into millions, thousands, hundreds, tens and singles
- And so on until all digits have been processed
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.
**free ctl-Opt dftActGrp(*no) option(*srcStmt : *nodebugIO); dcl-s forNumber zoned(13:2); dcl-ds *n; words varchar(200); showWords1 char(50) overLay(words : 3); showWords2 char(50) overLay(words : *next); showWords3 char(50) overLay(words : *next); showWords4 char(50) overLay(words : *next); end-ds; dsply 'Enter number: ' ' ' forNumber; words = currencyToWords(forNumber); dsply showWords1; dsply showWords2; dsply showWords3; dsply showWords4; *inLr = *on;
The following code shows the currencyToWords() subprocedure which accepts a complete number (including decimals) and two optional parameters to identify the names of the currency and the decimal units (which 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.
dcl-proc currencyToWords; dcl-pi *n varchar(200); number zoned(13:2) const; currency varchar(20) const options(*noPass: *trim); decimals varchar(20) const options(*noPass: *trim); end-pi; dcl-s forCurrency varchar(20) inz('Dollars'); dcl-s forDecimals varchar(20) inz('cents'); dcl-s cents zoned(2); 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-proc;
Finally, there is the code for the numberToWords() subprocedure which returns a text description of a requested integer.
The subprocedure contains declarations for
- Work fields for the segregated portions of the number (millions, thousands, hundreds, tens, and singles)
- The field that will contain the return value
- The definition of arrays (oneWords and tenWords) for all required words. Note that varying fields are used to eliminate the requirement for trimming when constructing the return text
The process in the subprocedure is as follows:
- Segregate the number into its component parts (millions, thousands, hundreds, tens and singles)
- For millions, thousand and hundreds, if the value is greater than 0, make a recursive call to the subprocedure (the subprocedure calls itself) to convert the segregated amount to words
- Convert the tens and units to words
- Return the result
The recursive calls cease when all segregated portions are zero – which has to happen since the number is being constantly segregated.
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-proc;
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, IBM Champion and author of Re-engineering RPG Legacy Applications, is a prominent consultant and trainer for application modernization and development technologies on the IBM Midrange. He is currently CEO of ComCon, a consultancy firm in Dublin, Ireland, and partner at System i Developer. He hosts the RPG & DB2 Summit twice per year with partners Susan Gantner and Jon Paris.