Guru: Fall Brings New RPG Features, Part 3
January 18, 2021 Jon Paris
In my previous tips (see Related Stories below), I covered the new BIFs and op-code options added with this release. This time I’m going to discuss a couple of new compiler options that focus on the conversion of character data to numeric.
While you may not have needed these capabilities to date, it is highly likely that you will in the near future. Why? Because of the rapid growth in the use of web services in the IBM i world. I don’t think I have talked to a single client in the last 12 months who was not already providing and/or consuming web services or planning to do so in the near future. When you use web services, you invariably need to convert from character to numeric because web service data is normally transmitted in character form.
RPG has a number of BIFs devoted to this task, the most commonly used of which are %DEC and %INT. These functions are also the underpinnings for the numeric conversions used by XML-INTO and DATA-INTO. Problems with handling “bad” numeric data are actually more problematic with the -INTO op codes than with the BIFs. With the BIFs we can test and manipulate the character strings to make them “legal” before attempting to convert them. That is not an option with the -INTO operations. They will simply error out.
So what types of situations do these new options address? There are two, actually. The first involves the handling of blank fields and the second the conversion of numbers that look like this: 12,345.67. Looks perfectly normal right? But what about this 12.345,67 or this 12 345,67?
If you live in North America you will almost certainly think the first is normal and the others a little strange. However, if you are based in Europe or, like me, have spent a lot of time there, you will recognize the second and third versions as normal. Most European countries use the comma as the decimal point and the period or blank as the thousands separator. (If like me you are immediately tempted to go research this topic you might find this article interesting: http://www.languageediting.com/format-numbers-eu-vs-us/.)
The reason that this multiplicity of formats presents a problem for RPG’s conversion BIFs is that RPG has always dealt with this difference by treating both the comma and the period as valid decimal points. So 12,345.67 is not invalid because RPG does not understand the comma, but rather because it does. As a result, it thinks the string contains two decimal points!
The two new compiler options are implemented as Expression Options (EXPROPT) parameters on the CTL-OPT or H-Spec.
*ALWBLANKNUM allows a blank field to be treated as zero. Without it RPG will treat any attempt to convert a blank string to numeric as an error. Note that with all of the conversion BIFs RPG has always happily ignored leading, trailing, and even embedded blanks. It just didn’t like completely blank fields. The fist time I encountered this is a real life situation was in processing an XML document where a supposedly numeric value would be blank when zero.
*USEDECEDIT tells the compiler to use the values specified by the DECEDIT keyword. In most cases this is not actually specified and so will default to *JOBRUN which in turn causes the QDECFMT system value to control the values used. The short version of all this is that most North American systems will default to period for the decimal point and European systems to default to comma, since these are the normal QDECFMT settings.
Here’s a simple program to demonstrate the use of these options.
Ctl-Opt ExprOpts(*AlwBlankNum : *UseDecEdit); dcl-s charBlank char(10) Inz; dcl-s charDecimal char(10) Inz(' 12,345.67'); Dcl-s value packed( 9: 2 ); Dcl-s quantity int(5); quantity = %Int( charBlank ); value = %Dec( charDecimal: 9: 2 ); Dsply ( 'If the options work this is zero: ' + %Char(quantity) ); Dsply ( ' and this will be 12345.67: ' + %Char(value) );
If you comment out the Ctl-Opt line you will see the error message: RNQ0105 “A character representation of a numeric value is in error” against either the %INT or %DEC operations, depending on which option you remove.
I should note at this point that in order to implement these particular functions, run time PTFs are needed in addition to the compiler PTFs. You’ll find the required numbers for 7.3 and 7.4 here: https://www.ibm.com/support/pages/node/6342819
I mentioned earlier that the -INTO operations are also impacted by these changes. I also mentioned that dynamic arrays and FOR-EACH really shine in such situations. For that reason I am going to wrap up this tip with an example that combines all of these new capabilities.
First here’s an extract from the XML that we will be processing:
<data> <item> <code>ABC</code> <number>1</number> <value>1,234.56</value> </item> <item> <code>DEF</code> <number> </number> <value>78.9</value> </item> </data>
Note that the value entry in the first item instance contains the thousands separator, and that the number element in the second item is blank. Without the new options, both of these would have caused errors.
Here’s the code that processes the file:
Ctl-Opt ExprOpts(*AlwBlankNum : *UseDecEdit); dcl-ds item dim( *Auto: 100 ) Qualified; code char(3); number zoned(5); value zoned(7:2); End-Ds; Dcl-ds entry likeDs(item); Dcl-s file varchar(40) Inz('/home/paris/xmlstuff/play.xml'); xml-into item %xml(file : 'doc=file case=any' ); For-Each entry in item; Dsply entry.code; Dsply entry.number; Dsply entry.value; EndFor;
As you can see, I have combined the two new conversion options, a dynamic array, and the FOR-EACH loop variant to process the document. You can also clearly see in this code what I mean by simplified field references. Had I coded the loop manually, references such as the simple entry.number would have needed to be item(element).number.
As before, you can see the current behavior by commenting out the relevant part of the Ctl-Opt. The XML-INTO operation will generate the error message RNQ0353 “XML document does not match RPG variable”. Further investigation will reveal that the reason code 8 is shown indicating that the identified field cannot be assigned to the target variable.
Wrap Up
So there you have it — the latest updates to RPG — and I love them! For those of you not yet on 7.4, the only downside is that you won’t be able to fully exploit these features until you move to 7.4, as dynamic arrays are not available on 7.3. Great reason though to nag the boss about it being time to upgrade!
Jon Paris is one of the world’s foremost experts on programming on the IBM i platform. A frequent author, forum contributor, and speaker at User Groups and technical conferences around the world, he is also an IBM Champion and a partner at Partner400 and System i Developer. He hosts the RPG & DB2 Summit with partners Susan Gantner and Paul Tuohy.
RELATED STORIES
Guru: Fall Brings New RPG Features, Part 2
Guru: Fall Brings New RPG Features
Guru: Dynamic Arrays Come To RPG
Jon, the DECEDIT keyword defaults to DECEDIT(‘.’).
DECEDIT(*JOBRUN) means that the job’s DECFMT value will be used. The job’s DECFMT value does come from the QDECFMT system value, but you can change it after the job has started.