For those of you like me who have been programming in IBM i and its predecessors for some time this might seem a simple subject matter. But I have received quite a few questions regarding numbers in the past few weeks. So I thought I would write something answering those questions and a bit more about the different types of numbers you will find in IBM i.
The most basic types of numbers everyone who has programmed on an IBM i has used are packed and zoned numbers. Zoned numbers are not anything new they are just the name that is used for what we use to call signed numbers. What is the difference? Why would I want to use packed rather than zoned? It really comes down to space. Back when computer first started using disk storage it was very expensive, therefore, everyone tried to reduce the amount of disk space they use. Someone came up with the idea of "packing" numbers to reduce the amount of space they use. "Packing" is a method I can store two numbers in each byte. Rather than spend paragraphs explaining what this is I think an example, like a picture, paints a thousand words.
Below is a very simple program that moves numbers into packed and zoned numbers.
01 **free 02 dcl-s Packed6 packed(6) ; 03 dcl-s Packed6a like(Packed6) ; 04 dcl-s Zoned6 zoned(6) ; 05 dcl-s Zoned6a like(Zoned6) ; 06 Packed6 = 123456 ; 07 Zoned6 = 123456 ; 08 Packed6a = -123 ; 09 Zoned6a = -123 ; 10 dump(a) ; 11 *inlr = *on ; |
Line 1: I only program in totally free RPG.
Lines 2 – 5: These are the variables I will be using. The first two are defined as packed variables, lines 2 and 3. The other two, lines 4 and 5, are defined as zoned, signed.
Lines 6 – 7: I move a positive number to these variables.
lines 8 and 9: I move negative numbers to these variables.
Line 10: I want to produce a program dump so that we can see what these variables hold.
This is copied from the program dump listing for the program's variables.
NAME ATTRIBUTES VALUE PACKED6 PACKED(6,0) 123456. '0123456F'X PACKED6A PACKED(6,0) -000123. '0000123D'X ZONED6 ZONED(6,0) 123456. 'F1F2F3F4F5F6'X ZONED6A ZONED(6,0) -000123. 'F0F0F0F1F2D3'X |
The part I am really interested is the hexadecimal value at the end of each line. This does show the value in each byte of the variable, but, in my opinion it is easier to understand if I format it like this:
Variable type | Positive value | Negative value |
Zoned | FFFFFF 123456 |
FFFFFD 000123 |
Packed | 0246 135F |
0013002D |
The first thing to notice is that the zoned variable is six bytes long, while the packed is four.
Why isn't the packed variable three long if you can store two number is each byte? You can store two numbers in each byte except for the last byte in a packed variable. The last half of the last byte contains a character that denotes the sign of the value. "F" indicates a positive value, "D" a negative. This makes all packed variables an odd value long. I can define the variable to be an even number, as I did in this example, but half a byte goes to waste. A wise old programmer told me when I first started learning RPG that all packed variables should always be odd numbers so that you do not waste that first half of a byte.
The sign code in a zoned variable is the first half of the last byte, as shown in the table above.
Over the years various other types of numbers have been introduced to the RPG language. As of IBM i 7.3 the current number types are:
Number type | Keyword | Maximum size |
Packed | packed | Up to 63 |
Zoned | zoned | Up to 63 |
Integer | int | 3, 5, 10, 20 1 |
Unsigned integer | uns | 3, 5, 10, 20 1 |
Binary | bindec | Up to 9 |
Float | Float | 4 or 8 |
1 Integer and unsigned cannot have decimal places.
To me the next question would be: what is the largest value these types of numbers can hold? The program below has variables defined of the various types and lengths. It move RPG hexadecimal value *HIVAL to each variable, which will be translated to variables maximum value.
01 **free 02 dcl-s Packed packed(63:0) ; 03 dcl-s Zoned zoned(63) ; 04 dcl-s Int3 int(3) ; 05 dcl-s Int5 int(5) ; 06 dcl-s Int10 int(10) ; 07 dcl-s Int20 int(20) ; 08 dcl-s Uns3 uns(3) ; 09 dcl-s Uns5 uns(5) ; 10 dcl-s Uns10 uns(10) ; 11 dcl-s Uns20 uns(20) ; 12 dcl-s Binary bindec(9) ; 13 dcl-s Float4 float(4) ; 14 dcl-s Float8 float(8) ; 15 Packed = *hival ; 16 Zoned = *hival ; 17 Int3 = *hival ; 18 Int5 = *hival ; 19 Int10 = *hival ; 20 Int20 = *hival ; 21 Uns3 = *hival ; 22 Uns5 = *hival ; 23 Uns10 = *hival ; 24 Uns20 = *hival ; 25 Binary = *hival ; 26 Float4 = *hival ; 27 Float8 = *hival ; 28 dump(a) ; 29 *inlr = *on ; |
I have sorted the results from the dump into the order I defined the variables in the program:
NAME ATTRIBUTES VALUE PACKED PACKED(63,0) ZONED ZONED(63,0) INT3 INT(3) 127 INT5 INT(5) 32767 INT10 INT(10) 2147483647 INT20 INT(20) 9223372036854775807 UNS3 INT(3) 255 UNS5 INT(5) 65535 UNS10 INT(10) 4294967295 UNS20 INT(20) 18446744073709551615 BINARY BIN(9,0) 999999999 FLOAT4 FLT(4) 3.402823466385E+038 FLOAT8 FLT(8) 1.797693134862E+308 |
I did not bother to put in the maximum value for the packed and zoned field as it is 63 9s, or 9 x 1063, which is too big to fit reasonably on this page.
Today's piece of useless information: 1063 is called 1 vigintillion.
If I replace the *HIVAL with *LOVAL I get the opposite, the lowest possible number in each variable.
NAME ATTRIBUTES VALUE PACKED PACKED(63,0) ZONED ZONED(63,0) INT3 INT(3) -128 INT5 INT(5) -32768 INT10 INT(10) -2147483648 INT20 INT(20) -9223372036854775808 UNS3 INT(3) 0 UNS5 INT(5) 0 UNS10 INT(10) 0 UNS20 INT(20) 0 BINARY BIN(9,0) -999999999 FLOAT4 FLT(4) -3.402823466385E+038 FLOAT8 FLT(8) -1.797693134862E+308 |
I have omitted the value for the packed and zoned as it is just the negative value of the positive value, and too many nines for this page.
The thing that caught my eye is that the negative value for the integer is one "smaller" than the positive value. For example, Int3 maximum value is 127, minimum value is -128!
What about moving the values between the types of numbers. It is really very simple, as they are all numbers we can move from one number type to another just with a standard expression:
01 **free 02 dcl-s Packed packed(7:2) ; 03 dcl-s Zoned zoned(5) ; 04 dcl-s Int5 int(5) ; 05 dcl-s Uns5 int(5) ; 06 dcl-s Binary bindec(9:2) ; 07 dcl-s Float4 float(4) ; 08 Packed = 123.45 ; 09 Float4 = Packed ; 10 Int5 = Float4 ; 11 Zoned = Int5 ; 12 Uns5 = Zoned ; 13 Binary = Uns5 ; 14 dump(a) ; 15 *inlr = *on ; |
When I sort the variables in order from the dump listing this is what I find:
NAME VALUE PACKED 00123.45 FLOAT4 1.234499969482E+002 INT5 123 ZONED 00123. UNS5 123 BINARY 0000123.00 |
I find it interesting that the float equivalent of 123.45 is not 1.2345E+002.
When the float is moved into the integer variable the decimal places are lost, therefore, they cannot be copied into the zoned or binary variables.
RPG does give a number of built in functions designed for being used when moving values into numeric variables:
- %DEC - Convert to packed decimal format
- %DECH - Convert to packed decimal format with half adjust
- %INT - Convert to integer format
- %INTH - Convert to integer format with half adjust
- %UNS - convert to unsigned format
- %UNSH - convert to unsigned format with half adjust
- %FLOAT - converting to float format
As the result of all of these built in functions is a number it should come as no surprise that I can use any of them to move a value into any of the number types.
Packed = %int('123.45') ; Int5 = %dec('123.45':5:2) ; Zoned = %uns('123.45') ; Uns5 = %float(Packed) ; |
The results are what I would have expected.
NAME VALUE PACKED 0000123.00 INT5 123 ZONED 00123. UNS5 123 |
OK, I have gone on long enough about numbers in RPG programs. I also want to talk about numbers in DDS file and Db2 for i DDL tables, and that will be the subject of the next post.
This article was written for IBM i 7.3, and should work for earlier releases too.
It looks like in your example with unsigned variables you defined them with INT instead of UNS. This is also reflected in the maximum values shown in the dump.
ReplyDeleteThank you for letting me know.
DeleteCorrection has been made.
Hello Simon.
ReplyDeleteThanks again for the post.
One remark: you declared the Unsigned variables as int type. That is probably why you get the same values (and even negative ones) as the Int variables.
Continue with your good work.
JJJ
Good catch, that explains why I get negative numbers. How can I have missed that!
DeleteThe code has been corrected, and the result is the *LOVAL for an unsigned integer is zero.
Thank you for letting me know my mistake.
This reminds me of working on Decision Data minis back in 1979, when our online system was written in Fortran V and the back end was written in Cobol.
ReplyDeleteFortran V could store a larger value in a Floating Decimal variable than you could store in a variable of the same length in Cobol, so every numeric field that was read from a file in Cobol had to be moved to a character field, have an extra character added to the left, and then redefined as a numeric field (or something like that).
Painful to program, and of course the reverse was true when you had to write numeric values back to a file.
Those were the days...
Very comprehensive, great goto reference for the future. Thanks.
ReplyDeleteHey, in your spare time, how about a tribute to the Classic RPG Edit Code, which most of the rest of the world has never enjoyed the luxury of..?
Really helpful. Thanks. You said that you will talk about numbers in DDS file in another post. Is that available?? I tried to search for it but can't find.
ReplyDeleteI do not have anything like that for DDS files.
DeleteMy thoughts are as DDS are no longer going to be enhanced, and the move (and guidance from IBM) is to move to DDL I concentrate my efforts there.
Why do I sometimes see 'count int(10:0);' ? Why do some people add a decimal size to an integer definition?
ReplyDeleteBecause the number of decimal places is optional for INT and UNS definitions.
DeleteA good addition to this article would be to add the CL/CMD data types. Especially when it comes to INT/UINT, they are very different. I have referred people to this page many times over the years....
ReplyDelete