Having discussed in previous posts about how to generate pseudo-random numbers using the CEERAN0 API in RPG and the SQL RAND function I received an email showing me a third way to do it, using a C function.
I would like to thank Bob Schmalandt for sending me the example program upon which this post is based upon.
There are two C functions that generate a pseudo-random number, and they both work in the same way.
- rand, is not threadsafe.
- rand_r, is.
In my examples below I will be using rand.
These functions generate a pseudo-random number between zero and the value RAND_MAX, which is the equivalent of a constant defined in C's stdlib.h. I found that the equivalent of C's stdlib.h file is the member STDLIB, in the source file H, in the library QSYSINC. There I found RAND_MAX is defined as:
#define RAND_MAX 32767
I could not find a way to change RAND_MAX, and after searching various C themed forums no-one is aware of a way to do so. Therefore, I would not recommend using this C function to generate pseudo-random numbers if the upper range you require is greater than 32,767.
In my example I am going to generate 100 pseudo-random numbers in the range 1 - 100, and I will write the numbers generated to the file, RANDOM_F. This file is the same one I used in my two previous posts I wrote about generating pseudo-random numbers, you will see the file's layout in Part I.
Below is the simplest form of my example program:
01 H dftactgrp(*no) bnddir('QC2LE') 02 FRANDOM_F O E DISK 03 D Random PR 10U 0 extproc('rand') 04 D i S 5U 0 05 D Random_Nbr S 10I 0 /free 06 for i = 1 to 100 ; 07 Random_Nbr = Random() ; 08 FLD1 = %rem(Random_Nbr:100) ; 09 if (FLD1 = 0) ; 10 FLD1 = 1 ; 11 endif ; 12 write RANDOM_FR ; 13 endfor ; 14 *inlr = *on ;
As we are calling the C function as a procedure we need to add the right keywords to our H-specs (Control specifications), see line 1.
- dftactgrp(*no) - as we are calling a procedure we cannot run the in the default activation group.
- bnddir('QC2LE') - this is the where the C/C++ run-time library reference is found, which is needed for rand.
On line 3 is where I have defined the procedure that will call rand. As rand is an external procedure it has to be defined using the EXTPROC keyword. It will return a 10 long unsigned integer (10U 0).
In my calculations I use a FOR to loop 100 times, line 6.
On line 7 I execute the rand by calling the procedure name Random(), and move the pseudo-random number into the field Random_Nbr.
As the value in Random_Nbr can be any number between 0 - 32,767 there are many times that it is outside my desired range, 1 - 100. If I divide Random_Nbr by 100 the remainder can be my pseudo-random number. I can do this using RPGLE's %rem() built in function, see line 8.
If the remainder is zero then in lines 9 - 11 I change it to 1.
On line 12 I write my pseudo-random number to my file.
So what does this look like? Here are the values from one of the times I ran this program:
When I ran the program other times I generated different numbers. Which brings up the issue of seeding. As you have seen in my example, above, I have not seed-ed rand.
IBM's website page for rand states:
If you do not call the srand() function first, the default seed is 1.
Note: srand is the C function to seed rand.
From the results of my testing I know that IBM's statement is incorrect. If it was correct then the same pseudo-random number would be produced. As different numbers are generated it must be using some other, unknown, method of seeding.
If you did want to use your own seeding you would use the C function srand. If I add the logic to use srand to my example program it would look like:
01 H dftactgrp(*no) bnddir('QC2LE') 02 FRANDOM_F O E DISK 03 D Seed PR extproc('srand') 04 D 10U 0 value 05 D Random PR 10U 0 extproc('rand') 04 D i S 5U 0 05 D Random_Nbr S 10I 0 06 D Seed_Nbr S 10I 0 /free 07 Seed_Nbr = %subdt(%timestamp():*ms) / 1000 ; 08 Seed(Seed_Nbr) ; 09 for i = 1 to 100 ; 10 Random_Nbr = Random() ; 11 FLD1 = %rem(Random_Nbr:100) ; 12 if (FLD1 = 0) ; 13 FLD1 = 1 ; 14 endif ; 15 write RANDOM_FR ; 16 endfor ; 17 *inlr = *on ;
On line 3 I have defined the procedure for srand. As it requires an input parameter line 4 defines that it is a 10 long unsigned integer field.
I am using the first three positions of the milliseconds of the timestamp. This is extracted using the %subdt build in function, then dividing the result by 1,000 to get just the first three positions. See line 7.
This is used to provide the seed, line 8.
I am not providing the seed before every time rand is executed, just as an initial value. If you wanted to provide your own seed every time rand is executed you need to be aware that if rand is executed with the same seed more than once it will return the same value.
You can learn more about this on the IBM web site:
- C functions rand and rand_r
- C functions srand
- RPGLE EXTPROC
- RPGLE DFTACTGRP
- RPGLE BNDDIR
- RPGLE %REM built in function
This article was written for IBM i 7.1, and it should work with earlier releases too.