Wednesday, November 13, 2013

Generating random numbers using a C function

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:

i Random_Nbr FLD1
1 16838 38
2 5758 58
3 10113 13
4 17515 15
5 31051 51

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:

 

This article was written for IBM i 7.1, and it should work with earlier releases too.

5 comments:

  1. Nice, clean method to use c calls in RPG. Well done.

    ReplyDelete
  2. Always handy! Now I can play the lotto in between coding sessions on the 400. ;-)

    ReplyDelete
  3. First point - binding directory QC2LE is only needed on V5R4 and earlier releases. Not needed since V6.

    Second IBM are correct - if not seeded the seed value on the first call is 1. BUT I suspect that you ran your tests in the default AG of QILE. So unless you kill the AG subsequent calls of rand() are not the first call from the perspective of the C runtime routines. If you had reclaimed QILE after each test run _or_ specified *NEW for the AG you would find that IBM's statement is correct. You can also test this by specifying a seed of 1.

    For some time IBM have recommended that rand() not be used. Can't find the reference but I was told about this some 15 years ago. There is an issue in rand() that if I recall correctly had to stay for compatibility reasons. Your original approach of using CEERAN0 is the approved method.

    ReplyDelete
    Replies
    1. Jon thank you for the feedback. I did not know that you no longer need to bind in QC2LE.

      I agree with you, and I would use the CEERAN0 in preference to this one.

      Delete
  4. Simon, thanks for the acknowledgement. Wish I could acknowledge who I got it from, but that's lost to the ages.. I sure as heck didn't figure it out myself. Really enjoy your blog

    ReplyDelete

To prevent "comment spam" all comments are moderated.
Learn about this website's comments policy here.

Some people have reported that they cannot post a comment using certain computers and browsers. If this is you feel free to use the Contact Form to send me the comment and I will post it for you, please include the title of the post so I know which one to post the comment to.