Many of us handle data coming from non IBM i sources, whether it be files sent from other types of systems or from web applications. While RPG has strict rules of what can be done with numbers, other systems do not. In my experience it is not uncommon to receive blank numeric data, or a number that contain thousand separators. We have all come up with our own ways of handling this kind of issues, but it is nice to see that IBM have introduced a couple new expression options to make this easier for us to work with.
Both come as part of the latest round of Technology Refreshes, IBM i 7.4 TR3 and 7.3 TR9. The these additions have been added to the existing Expression Options control option, EXPROPTS:
- EXPROPTS(*ALWBLANKNUM): Allows RPG to handle a blank in a numeric variable
- EXPROPTS(*USEDECEDIT): Gives RPG the ability to handle numeric strings containing thousand separators
EXPROPTS(*ALWBLANKNUM): Allows RPG to handle a blank in a numeric variable
I am sure we have all had situations where we were promised numeric data in a column or field in a file from another application, and then found that it contains numbers and blanks too. It only take a few lines of code to check if the field is blank, and make the value zero. Now IBM has made it possible to just move the character string of the "number" into a numeric field using the standard numeric conversion Built in Functions and the XML_INTO and DATA-INTO operation codes.
Before this expression option if I tried to move a blank into a numeric field I would receive the following runtime error:
Message ID . . . . : RNX0105 Severity . . . . . : 99 Message type . . . : Diagnostic Date sent . . . . : DD/DD/DD Time sent . . . . : TT:TT:TT Message . . . . : A character representation of a numeric value is in error. Cause . . . . . : A conversion from character to numeric found data that was not valid. Recovery . . . : Correct the numeric data. |
Now I can do this can be error free:
01 **free 02 ctl-opt expropts(*alwblanknum) ; 03 dcl-s wkChar char(3) ; 04 dcl-s wkNbr1 packed(3) ; 05 dcl-s wkNbr2 like(wkNbr1) ; 06 dcl-s wkInt int(5) ; 07 dcl-s wkUns1 uns(5) ; 08 dcl-s wkUns2 like(wkUns1) ; 09 dcl-s wkFloat float(8) ; 10 wkChar = ' ' ; 11 wkNbr1 = %dec(wkChar:3:0) ; 12 wkNbr2 = %dech(wkChar:3:0) ; 13 wkInt = %int(wkChar) ; 14 wkUns1 = %uns(wkChar) ; 15 wkUns2 = %unsh(wkChar) ; 16 wkFloat = %float(wkChar) ; 17 *inlr = *on ; |
Line 1: If I am writing RPG it has to be totally free format.
Line 2: I am only showing the one control option for what I am talking about: *ALWBLANKNUM. It is good to see IBM have given it a name that explains what it does.
Lines 3 – 9: Definitions for the character and numeric variables I will be using in this example program.
Line 10: I know this is redundant as when the wkChar is defined, on line 3, it will contain blank. I just want to add this line of code to reinforce that this character variable contains blank.
Lines 11 – 16: I am using the various number conversion BiFs to convert the character variable to the various number formats.
Having compiled this program, added a debug breakpoint on line 17, and called the program I can display the values in those numeric variables:
WKNBR1 = 000. WKNBR2 = 000. WKINT = 0 WKUNS1 = 0 WKUNS2 = 0 WKFLOAT = 0.000000000000E+000 |
As you see RPG has translated the blank in wkChar to zero in all of the numbers.
EXPROPTS(*USEDECEDIT): Gives RPG the ability to handle numeric strings containing thousand separators
It is not possible to move a character value into numeric variable using any of the conversion BiFs if the "number" contains thousand separators.
This new expression option requires a second new control option Decimal Edit character, DECEDIT, to accompany it. Let me give my example program, and then explain what these new control options do and why.
01 **free 02 ctl-opt expropts(*usedecedit) decedit(*jobrun) ; 03 dcl-s wkChar char(15) inz('1,234,567.89') ; 04 dcl-s wkNbr1 packed(12:2) ; 05 dcl-s wkNbr2 like(wkNbr1) ; 06 wkNbr1 = %dec(%xlate(',':' ':wkChar):12:2) ; 07 wkNbr2 = %dec(wkChar:12:2) ; 08 *inlr = *on ; |
Line 2: Here we have the new expression option: *USEDECEDIT. And following it the decimal edit option: DECEDIT. I have the decimal edit set *JOBRUN which means it will use the decimal rules set for this job. I will explain below what other values can be used.
Lines 3 - 5: The variable definitions. Here I have initialized the value of wkChar in its definition.
Line 6: This is the way I have been removing the thousand separators from my character "numbers". I have a translate BiF nested within a convert to decimal BiF. The %XLATE replaces the commas with spaces:
'1 234 567.89 ' |
And this string can now be converted to a number by the decimal BiF.
Line 7: With these new control options I don't have to that. I don't have to replace the commas. The convert to decimal knows what to do thanks to those control options.
After I compiled the program, started debug, and put a breakpoint at line 8. When I reach the breakpoint I can display the contents of those numeric variables:
WKNBR1 = 0001234567.89 WKNBR2 = 0001234567.89 |
The numbers are identical.
Above I mentioned that there were different values that could be placed in the DECEDIT control option. To demonstrate I create a simpler program than the previous one:
01 **free 02 ctl-opt expropts(*usedecedit) decedit(*jobrun) ; 03 dcl-s wkNbr1 packed(3:1) ; 04 dcl-s wkNbr2 like(wkNbr1) ; 05 wkNbr1 = %dec('1,2':3:1) ; 06 wkNbr2 = %dec('1.2':3:1) ; 07 *inlr = *on ; |
Lines 5 and 6: Depending on the value in the DECEDIT these statements will produce different results.
In this first example using *JOBRUN in the decimal edit the thousand and decimal separators are taken from the job. As I am in the USA the thousand separator is a comma, and decimal separator is a period. When I look at the values in variables at a debug breakpoint I added at line 7 I see:
WKNBR1 = 12.0 WKNBR2 = 01.2 |
The thousand separator of comma has been ignored, therefore, wkNbr1 contains the number 12. wkNbr2 contains 1.2 as the decimal separator is a period.
Now the only change I make to the program is to change the value in the decimal edit control option.
02 ctl-opt expropts(*usedecedit) decedit('.') ; |
This tells the program that the decimal separator is a period, therefore, the thousand separator must be a comma. No surprise that my results are:
WKNBR1 = 12.0 WKNBR2 = 01.2 |
Not all countries use the comma and period in the same way the USA does. Some use the period as the thousand separator and the comma as the decimal separator. All I need to do is to change line 2 again:
02 ctl-opt expropts(*usedecedit) decedit(',') ; |
Now the results are reversed.
WKNBR1 = 01.2 WKNBR2 = 12.0 |
As the value moved into wkNbr1 contains the comma it is taken as the decimal separator.
The period in the value converted into wkNbr2 is interpreted as the thousand separator, and is ignored.
These two options can be combined into the same expression option:
02 ctl-opt expropts(*alwblanknum:*usedecedit) decedit(*jobrun) ; |
I am going to find these very useful in my daily work. I will be adding these to the source member I have that contains all of the control options I want inserted into my RPG programs when I use a copy or include statement.
You can learn more about this from the IBM website:
This article was written for IBM i 7.4 TR3 and 7.3 TR9.
Hi Simon. The DECEDIT keyword isn't new. I think it has been around since the beginning of ILE RPG.
ReplyDeleteYou don't actually have to specify the DECEDIT keyword when you code OPTIONS(*USEDECEDIT), if the default of DECEDIT('.') is ok for your program.
Merci pour l'info
ReplyDeleteSimon, great read and white paper. You’re correct, with data coming from multiple sources and no error handling / validation of the data , these are great functions and BIFs to use. Again,thanks for sharing.
ReplyDeleteNow if we could just get *alwblanknum added to SQL....
ReplyDeleteLove this
ReplyDeleteI always get a RNF1311 Error message for "expropts(*usedecedit)". As well expropts(*alwblanknum). I am really not sure what am i doing wrong. The message says, the keyword is not valid for expropts. how?
ReplyDeleteCheck with your Sys Admin that you have the correct PTFs. You should have the RPG PTFs for 7.4 TR3 or 7.3 TR9 and greater.
Delete