Every once in a while I get several people asking me the same question. As the examples accompanying tend to be the same I assume that this question has become part of some organization's interview question list. Recently it has been questions about using packed decimal variables with the Submit Job command, SBMJOB.
Most of us when we started on the IBM i (or one of its earlier forms) tried a SBMJOB with a packed decimal variable. The submitted job produced the following error message: "MCH1202: Decimal data error". After trying several different ways we admitted defeat, convert the packed decimal variable to character and used that in the SBMJOB.
What we are being foiled by is the command itself, when it is executed SBMJOB converts all decimal packed variables to a packed decimal that is 15,5. If our packed decimal variable is not 15,5 the value is corrupted by the conversion when it is passed to the called program.
Having spent some time passing data with test programs I have found there are three ways I can pass a packed decimal variable using the Command parameter of SBMJOB:
- Convert it to character
- Move it to a packed decimal with the size 15,5 and use the moved variable
- Include it as a sub field of a data structure
Let me give you an example. The CL program, below, uses all of these three ways.
01 PGM 02 DCL VAR(&P1) TYPE(*DEC) LEN(5 2) 03 DCL VAR(&P2) TYPE(*CHAR) LEN(6) 04 DCL VAR(&P3) TYPE(*DEC) LEN(15 5) 05 DCL VAR(&P4) TYPE(*CHAR) LEN(10) 06 DCL VAR(&P4_1) TYPE(*CHAR) STG(*DEFINED) LEN(6) DEFVAR(&P4) 07 DCL VAR(&P4_2) TYPE(*DEC) STG(*DEFINED) LEN(5 2) DEFVAR(&P4 7) 08 CHGVAR VAR(&P1) VALUE(123.45) 09 CHGVAR VAR(&P2) VALUE(&P1) 10 CHGVAR VAR(&P3) VALUE(&P1) 11 CHGVAR VAR(&P4_1) VALUE(&P2) 12 CHGVAR VAR(&P4_2) VALUE(&P1) 13 SBMJOB CMD(CALL PGM(CLPGM) PARM(&P1 &P2 &P3 &P4)) 14 SBMJOB CMD(CALL PGM(RPGPGM) PARM(&P1 &P2 &P3 &P4)) 15 ENDPGM |
Line 2: This is the packed decimal variable that I am going to use as the original value I am trying to pass via the SBMJOB to the submitted programs.
Line 3: The character variable is one position/character longer than the original value so that it can accommodate a decimal point, if one is needed.
Line 4: This packed decimal variable is 15,5.
Lines 5 – 7: This is the equivalent of a data structure in CL. The "data structure" contains two "sub fields", the first starts at the first position, line 6, and the second at the seventh position, line 7. For more information about using these "data structures" see the post Data Structures in CL.
Line 8: The desired number is moved to the first packed decimal variable, &P1.
Lines 9 – 10: The value in &P1 is moved to &P2, character, and &P3, packed decimal 15,5.
Lines 11 – 12: Moving values to the "sub fields" of the "data structure".
Lines 13 - 14: I am going to use two SBMJOBs, the first to another CL program and the second to a RPG program.
The submitted CL program is very simple:
01 PGM PARM(&P1 &P2 &P3 &P4) 02 DCL VAR(&P1) TYPE(*DEC) LEN(5 2) 03 DCL VAR(&P2) TYPE(*CHAR) LEN(6) 04 DCL VAR(&P3) TYPE(*DEC) LEN(15 5) 05 DCL VAR(&P4) TYPE(*CHAR) LEN(10) 06 DCL VAR(&P4_1) TYPE(*CHAR) STG(*DEFINED) LEN(6) DEFVAR(&P4) 07 DCL VAR(&P4_2) TYPE(*DEC) STG(*DEFINED) LEN(5 2) DEFVAR(&P4 7) 08 DMPCLPGM 09 ENDPGM |
Line 2 - 7: The parameters passed to this program are defined with the same attributes as they had in the calling program.
Line 8: I am using the Dump CL Program command, DMLCLPGM, to produce a dump of the program to display the contents of the variables. If you have not used the DMPCLPGM you can learn about it in the post CL's DMPLCPGM command.
If I look at the spool file produced by the DMPCLPGM I can see what the variables contained:
Variable Type Length Value &P1 *DEC 5 2 0 &P2 *CHAR 6 '123.45' &P3 *DEC 15 5 123.45 &P4 *CHAR 10 '123.45 ¬ ' &P4_1 *CHAR 6 '123.45' &P4_2 *DEC 5 2 123.45 |
Even though &P1 is shown as having a value of zero if it used it will produce the "Data decimal error", as it contains an invalid value passed from the calling program.
The 15,5 packed decimal variable, &P2, and packed decimal "sub field", &P4_2, both contain the valid packed decimal value.
I decided to use the Main procedure in my RPG program to give another example of how to use it. If you are unfamiliar with using the Main procedure see the post Getting off the RPG cycle.
01 ctl-opt main(Main) ; 02 dcl-pr Main extpgm('RPGPGM') ; 03 *n packed(5:2) ; 04 *n char(6) ; 05 *n packed(15:5) ; 06 *n char(10) ; 07 end-pr ; 08 dcl-proc Main ; 09 dcl-pi *n ; 10 P1 packed(5:2) ; 11 P2 char(6) ; 12 P3 packed(15:5) ; 13 P4 char(10) ; 14 end-pi ; 15 dcl-ds IncomingP4 ; 16 P4_1 char(6) ; 17 P4_2 packed(5:2) ; 18 end-ds ; 19 IncomingP4 = P4 ; 20 dump(a) ; 21 end-proc ; |
Lines 9 – 14: The incoming variables from the SBMJOB are defined in the procedure interface, DCL-PI.
Lines 15 – 19: I have created this data structure to break P4 into the sub fields that were used in the calling program.
Line 20: I am using the Dump operation code to produce a dump of the program to display the contents of the variables. If you have not used the DUMP operation code you can learn about it in the post RPG's DUMP operation code.
When I look in the spool file produced by the DUMP I see that the contents of the variables are the same as they were in the submitted CL program.
INCOMINGP4 DS P4_1 CHAR(6) '123.45' P4_2 PACKED(5,2) 123.45 P1 PACKED(5,2) 000.00 P2 CHAR(6) '123.45' P3 PACKED(15,5) 0000000123.45000 P4 CHAR(10) '123.45 ¬ ' |
To answer the question that I used for this post, if I wanted to pass a packed decimal variable using the SBMJOB command I would use a packed decimal variable that is *DEC(15 5).
You can learn more about the SBMJOB command from the IBM website here.
This article was written for IBM i 7.2, and should work for earlier releases too.
Update
I received this from Greg Veal as a comment on this post, but I thought the information contained within would be better server as part of the post. Thank you Greg for the information.
Simon,
Thanks for this one. I have been preaching 15,5 for decades, but SBMJOB with packed decimals seems to continue to present a problem to people. However, just to clarify:
It is not accurate to say, "...when it is executed, SBMJOB converts all decimal packed variables to a packed decimal that is 15,5."
To understand what's really going on here, we need to recognize that the SBMJOB problem is 100% analogous to the problem of typing "Call MyPgm Parm(123.45)" on a command line when MyPgm is expecting 5,2.
Why? SBMJOB actually converts the CMD() parm into the RQSDTA() (Request Data) parm (prompt SBMJOB and page down to the end of the parms). The Request Data is a character string that becomes the message text on a Request message in the job log of the submitted job. (Display the job log of a job on hold in a job queue, and you can see this string on the request message that is there! Really! this all becomes much more clear when you can look at it directly!)
When the batch job starts, a CPP (Command Processing Program), such as QCMD, receives the message and executes the command, much the same as you might by passing a command string into QCMDEXC. Or typing the string on a command line.
So any way you look at it, "123.45" is now a numeric "literal", NOT a variable. And here's the rub: When MyPgm is called, the system stores the value of the numeric literal parm in memory and passes a pointer to MyPgm with the location. Of course, the system has no way to know what size decimal variable MyPgm was written to receive, so it stores it in memory as the "default" size of 15,5 (i.e. x'000000012345000F'.
(NOTE: If you don't specify the size of a decimal variable in a CL pgm, it "defaults" to 15,5.)A 5,2 packed decimal takes only 3 bytes (x'12345F'). So when MyPgm accesses the 3 bytes of storage at the pointer location, it finds x'000000'. There is no sign (F,D, or C) in the low order position, hence MCH1202, invalid decimal data! But of course, declaring the parm variable as 15,5 lets this all work beautifully for SBMJOB, QCMDEXC, and a command line as well! (NOTE: On a command line, you could also use hex:
e.g. Call MyPgm parm(x'12345F'), a useful technique for unit testing.)And now, you know . . .
The REST of the story! : ))
Option 4: Build a command for the program to be called and let IBM i do the conversion work.
ReplyDeleteYes!
DeleteThis is good way !
Sorry, Jon, I can't agree. Though this works perfectly, and I have taught it for years as an alternative method, I've found that most people don't really want to have commands for dozens or hundreds of submitted jobs - including yours truly.
DeleteHi Simon
ReplyDeleteIf you say 'when we started on the IBM i (or one of its earlier forms)'
it seems to me that you are referring to the hardware as in the following sentence:
'when we started on the AS/400'.
IBM i is not the name of the hardware, it is the name of an EBCDIC-based operating system
that runs on IBM Power Systems.
Wouldn't it be better to say:
'when we started on IBM Power Systems (or one of its earlier forms)'? ;-)
Regards
Jan
Perhaps this is a case of a mischosen word, I could have used the name of the operating system, “OS400”, rather than the name of the server, “AS400”.
DeleteLong time readers of this blog do know that I am aware that IBM i is one of the operating systems that runs on the PowerSystems servers. I have made this clear in previous posts upon this blog, and in many postings I have made in social media.
Nice article Simon
ReplyDeleteGaurav Singh
Thanks again Simon for the great contributions and the sharing that you provide to all of us.
ReplyDeleteOh yeah, Simon, almost forgot! There is no need in the submitting pgm to move the 5.2 to a 15.5. ALL numerics of any size get converted to numeric literals within the RQSDTA character string - the length doesn't matter yet. When QCMD calls the submitted pgm is when (any size) numeric literals get stored in memory as 15.5. So the received variable in the submitted pgm is the only one that must be 15.5. ☺
ReplyDelete(and you thought we were done with this! : )