Converting Numeric to Character
Until recently, it had been difficult to convert between numeric and character values in languages other than RPG. In RPG, we simply used the MOVE opcode and numeric-to-character or character-to-numeric conversion was magically done for us.
Today, many languages have built-in functions or implicit conversions between character and numeric. For example, in the C language, the atoi() and itoa() functions convert numeric into character format and character into numeric format, respectively. The advantage of these functions over, say, the MOVE opcode, is that they also do some limited handling of embedded blanks, commas, decimal points, and the so-called status (positive or negative sign). You can't do that with a simple MOVE opcode.
In RPG IV, IBM added two operation codes that would convert numeric values into character values and allow you to mask or edit the resulting values. That way, the character format appears just as it would have, had it been output using RPG IV output specification forms and an edit code or edit word.
The %EDITC (edit code) built-in function's purpose is to convert a numeric value (or the result of a numeric expression) into a character format. Its second parameter is used to control the formatting of the resulting value. It can be any available RPG IV edit code. So, for example, the following code would yield a result of 3,210.50 in TextField:
C Eval TextField = %editc( amtdue : 'J')
One anomaly of the %EDITC built-in function is that the resulting character string that it returns is as long as the original numeric value, plus the edit codes and decimal positions. So if the AMTDUE field used in the example was a nine-position packed field with two decimals, the resulting value would be 12 positions in length, as follows:
A design decision had to be made to either leave in the leading blanks or truncate them. There are good reasons both to leave them in and to take them out. But the decision was made so that when several values are being converted, the resulting character string length would be consistent.
To truncate the leading blanks, wrap the %EDITC built-in function within a %TRIML built-in function, as follows:
This will eliminate the leading blanks from the result value.
In OS/400 V4R4, IBM introduced the %CHAR built-in function. %CHAR does a fine job of converting numeric values to character. But unlike %EDITC, the %CHAR function strips off leading blanks, and it does not allow you to specify a customized edit code. Edit code L is effectively used by %CHAR.
In the example, %CHAR(amtdue) would return 3210.50. In addition, %CHAR can be used within concatenations. This makes it incredibly easy to create messages intended for humans to read. For example:
C ' was not found.'
The %CHAR built-in function converts the numeric INVNBR field into character format, stripping off the leading blanks and zeros. So the end user might see something like this:
Converting Character to Numeric
What if an end user types 32.50 into a Web page? This value will arrive in your program as character data; literally, '32.50' is sent. But that value needs to be stored in the database as a seven-digit packed field with two decimals. So how do you convert it?
Before you answer, consider this: The end user might enter the value as 32.50 or 1,320.00 or .50 or 0.50 or 1.23- or as simply the number 10. So how can it be converted successfully in all situations?
If you are running OS/400 V5R2, you can use an enhancement to the %DEC built-in function. %DEC (under V5R2) will convert a character value containing valid numeric data into a numeric value. You must specify the resulting field's length and decimal positions, however. Here's an example:
D TextValue S 12A Inz('1,320.50')
C Eval Amtdue = %Dec(TextValue : 7 : 2)
The first parameter of %DEC is the character string that is converted. The second and third parameters are the length and decimal positions to use for the conversion. All parameters are required.
Alternatively, you can specify the second and third parameters by using the relative size of the result field. To do this, substitute the %LEN and %DECPOS built-in functions in place of the hard-coded length and decimal positions, as follows:
D TextValue S 12A Inz('1,320.50')
C Eval Amtdue = %Dec(TextValue :
C %len(amtdue) : %decpos(amtdue))
This may be a bit more complicated and ugly, but it provides more flexibility (read: forgiveness) when you copy the code into another program.
The enhancement to %DEC is also provided for in the %INT built-in function. Both are welcome and long overdue. However, it bothers me that these non-operating-system-specific enhancements are not ported back to V4R5 or even V5R1, thus making it very unlikely that you'll actually use either of them in the next year or more.
So, what do we do about this situation? We implement similar functions ourselves--using existing interfaces--and make it work all the way back to V3R7 and possibly even V3R2.
Do It Yourself
- Converting integers or whole numbers
- Converting numbers with decimal notation, such as dollar values
Converting integers or whole numbers is extremely easy in RPG IV. Simply prototype one of the C language runtime functions and then call it. It's that easy!
Listed in Figure 1 is the RPG IV source for the C language atoi() function. This is just the prototype, but that's all that is required to call the function, except, of course, the ever-present BNDDIR('QC2LE') keyword in the header specification.
Figure 1: Prototype for atoi (character to integer) function
The function "atoi" actually means "ASCII to integer," but we're not working with an ASCII character set, so we refer to it as "character to integer." In RPG IV, an integer is an "I" data type with a length of 3, 5, 10, or 20 and a decimal position that is always 0.
The length of integer fields in RPG IV is somewhat of a misnomer. Integers are traditionally referred to by their byte count, not the number of digits they hold. So the declared lengths are not the length of the field in bytes. In RPG IV, we have 1-, 2-, 4-, and 8-byte integers, which correspond to the 3, 5, 10, and 20 position "I" fields. For example, a 3i0 field is a 1-byte integer, and a 10i0 field is a 4-byte integer. The table listed in Figure 2 defines each of the integer field lengths and the corresponding data types in RPG IV.
As Declared in RPG
-256 through 256
-32 768 through 32 767
-2 147 483 647 through 2 147 483 647
-9 223 372 036 854 775 808 through
9 223 372 036 854 775 807
Figure 2: Integer data types' lengths and ranges
The following illustrates the use of the atoi() procedure in RPG IV.
0002 D InvNbr S 7P 0
0003 D TextValue S 12A Inz('10425')
0004 C Eval InvNbr = atoi(TextValue)
The atoi() function (line 4) converts the textual '10425' into a numeric value and assigns that value to the INVNBR field. Note that assigning the integer value to the packed decimal field is perfectly legal.
There is an obvious problem with atoi(). It handles numbers up to 10 digits in length. This is fine, except it won't work with some telephone numbers or other long numeric values. The IBM ILE C compiler resolves this issue by supporting another function, named atoll(). This function works exactly the same as atoi(), except that it handles much larger values. Up to a 19-digit numeric value may be converted using atoll().
The following illustrates the use of the atoll() procedure in RPGIV.
0002 D Phone S 13P 0
0003 D TextValue S 20A Inz('0118005529402')
0004 C Eval InvNbr = atoll(TextValue)
There is nothing that prevents you from using atoll() exclusively. It works just as well with short integers as it does with long integers (actually they are referred to as "long, long integers," hence the atoll nomenclature). The prototype for atoll() is listed in Figure 3.
Figure 3: Prototype for the atoll procedure
Remember, when you're using a C language function, you are using a case-sensitive language. So the procedure name "atoll" must be in lowercase when specified as the parameter of the EXTPROC keyword (line 1).
That takes care of integers, but what about currency values--dollars and cents? How do you convert something like "123.45" to a number?
The C language has another function named atof(). This function converts a character string containing numeric data to a floating point value. The atof() (convert ASCII to floating point value) function works pretty well with simple values, but when accuracy is important, atof() can not be relied upon. Figure 4 contains the prototype for the atof() function in RPG IV.
Figure 4: Prototype for the atof procedure
Instead of atof(), there are other interfaces that can accomplish a similar, yet more accurate, result. The AS/400 and iSeries have something that is at a lower level than the C language, and that is the Machine Interface (MI). In effect, this is OS/400 Assembly language. While I don't want to get into the virtues of MI programming, I do advocate using a bit of MI now and then, especially today, since you can call an MI instruction directly from within RPG IV.
The MI instruction CVTEFN (Convert external form to numeric) allows you to
convert from character to numeric. The resulting value can be virtually any
numeric data type and length. The documentation on CVTEFN states the
"Scans a character source for a valid decimal number in display format, removes the display character, and places the result in receiver."
The syntax diagram for CVTEFN is as follows:
unsigned int *src_length,
This seems complex, but it isn't. It just used different names for parameters. For example, _SPCPTR is a pointer to a variable. In RPG IV, that means it's a regular parameter. To create a parameter list to call CVTEFN, the RPG IV code in Figure 5 could be used.
Figure 5: Prototype to call CVTEFN
In Figure 5, lines 1 through 6 contain a data structure that is used as the second parameter for the call to CVTEFN. Since data structures cannot be directly declared on parameter lists, we need to declare the data structure separately. If you are running OS/400 V5R1 or later, the LIKE keyword used on line 6 may be replaced with the LIKEDS keyword.
Lines 7 through 12 illustrate the procedure prototype/parameter list in RPG IV for a call to CVTEFN. We are actually calling '_CVTEFN', which is a little different from the other CVTEFN MI instructions. The leading underscore indicates that this function is an in-line function; its code is actually inserted into the module we are creating, thus providing faster processing since the overhead of an additional procedure call is avoided. Just how much it saves us in RPG IV is subjective.
I've create a wrapper procedure to call _CVTEFN that makes using the MI function as easy as calling the atoi() function.
Listed in Figure 6 is the source code for an RPG IV module that can be compiled and used with your existing programs. It can be compiled as a *MODULE object and bound by copy, or the module can be linked into a *SRVPGM (service program) and bound by reference.
The syntax to call this wrapper procedure, named CHARTONUM, is as follows:
The CharToNum procedure accepts a character string input field or literal value and returns a packed decimal return value.
The first parameter may be any valid character string that contains a numeric value. The character string may contain an edited form of a number or a simple numeric value. Blanks, commas, periods, currency symbols, and a status (positive or negative indication) may be embedded in the string.
The second parameter is an optional edit mask, and it may contain the symbols used for currency symbol, thousands separator, or decimal notation. If unspecified, the default mask is '$,.'. To change this default, modify the DFTEDITMASK named constant on line 23 in Figure 6.
Figure 6: Source module for the CHARTONUM procedure
I tested this procedure using the following eight test
D szTest1 S 20A Inz('3,741.52')
D szTest2 S 20A Inz('$3,741.52')
D szTest3 S 20A Inz('3,741.52-')
D szTest4 S 20A Inz(' 3741')
D szTest5 S 20A Inz(' -3,741.52')
D szTest6 S 20A Inz('$-3,741.52')
D szTest7 S 20A Inz(' $3,741.52-')
D szTest8 S 20A Inz('-$3,741.52')
The first seven tests converted to numeric as expected. However, the eighth test failed. Apparently CVTEFN does not like the negative sign in front of the currency symbol. Also, in other testing, phone number or social security number editing sequences failed when using CVTEFN, so we're limited to integers and currency values.
The bottom line is CharToDec is a great asset to your RPG programming tool kit. It easily converts character strings containing integers and decimal values to numeric without much trouble.