Skip to main content

Random and ‘Almost Random’ Numbers on z/OS

From COBOL to C, there are many options for creating a random number. Learn advantages, disadvantages and prerequisites of each method, along with tips and tricks.

Layered gold, green and blue squares against a red background

Random numbers can come in handy. They can be used to create a unique filename, generate a random new password, or randomly select a record. The good news is that we can obtain random numbers on z/OS. Well, at least some of them are random.

COBOL and RANDOM

A quick look at the IBM Enterprise COBOL manuals will show that there is a RANDOM function: sounds perfect. So, let’s run some code to use this function:
 
Compute WS-RNDNUM-L1 = Function RANDOM(1)
Display 'Number 1: ' WS-RNDNUM-L1        
                                         
Compute WS-RNDNUM-L1 = Function RANDOM(1)
Display 'Number 2: ' WS-RNDNUM-L1        
                                         
Compute WS-RNDNUM-L1 = Function RANDOM(1)
Display 'Number 3: ' WS-RNDNUM-L1        

We call RANDOM three times, displaying the results. When we run it, we get something like:
Number 1:  .88541431021197434E 00
Number 2:  .88541431021197434E 00
Number 3:  .88541431021197434E 00

The good news is that we get a real number between 0 and 1. The bad news is that every number is the same: not very random.

The RANDOM function doesn’t output a random number. Rather, it returns a pseudo-random sequence of numbers. The parameter passed to RANDOM (1 in our case) is the “seed.” COBOL uses this to start the sequence. The idea is that we can reproduce the same sequence if we use the same seed. 

Because we specified a seed for every RANDOM call, COBOL restarted the sequence each time. To get a sequence with different numbers, we only specify the seed for the first call:
 
Compute WS-RNDNUM-L1 = Function RANDOM(1)
Display 'Number 1: ' WS-RNDNUM-L1        
                                         
Compute WS-RNDNUM-L1 = Function RANDOM
Display 'Number 2: ' WS-RNDNUM-L1        
                                         
Compute WS-RNDNUM-L1 = Function RANDOM
Display 'Number 3: ' WS-RNDNUM-L1        

Now we get:
Number 1:  .88541431021197434E 00
Number 2:  .12016591714702822E 00
Number 3:  .60956745949088011E 00
A bit better: every number is different. If we run this code again, we get the same result:
Number 1:  .88541431021197434E 00
Number 2:  .12016591714702822E 00
Number 3:  .60956745949088011E 00
If we run this code (with the same seed) on a different z/OS system or CEC, we also get the same sequence of numbers: COBOL uses a mathematical process to generate the sequence that’s independent of the hardware or z/OS. 

The COBOL RANDOM function gives us pseudo random numbers between 0 and 1. If we want larger numbers, we can multiple our random number. For example, the following code get a random integer between 0 and 100:
 
01 WS-RNDNUM          Pic 9(2).
Compute WS-RNDNUM-L1 = Function RANDOM(1)

Compute WS-RNDNUM = WS-RNDNUM-L1*100     

Other Languages With RANDOM Functions

COBOL isn’t the only programming language with a RANDOM function or statement. In fact, most languages have something. For example:
  • PL/I: RANDOM
  • C/C++: random(), rand(), rand_r(),  drand48(), erand48(), lrand48(), mrand48(), nrand48(), jrand48()
  • SAS: rand
  • REXX: RANDOM
And the options don’t stop there. Db2 has a RANDOM function, and z/OS Language Environment provides the CEERAN0 program. Let’s look at some COBOL code to call CEERAN0:
 
* Get random number between 0 and 1.                       
      Call 'CEERAN0' Using WS-SEED, WS-RNDNUM-L1, WS-FC    
* Handle any errors calling our function                   
         On Exception                                      
            Display 'CBLRAND: Error calling CEERAN0'       
            Move 12 To WS-FC-SEVERITY                      
      End-Call                                             
                                                          
      If WS-FC-SEVERITY = 0 Then                           
* Call successful                                          
         Display 'LE Number 1: ' WS-RNDNUM-L1 
      Else                                                
* Error attempting to call                                 
         Display 'CBLRAND: Call to CEERAN0 failed, msg '   
            WS-FC-MSG-NO                                   

      End-If    
Like the COBOL RANDOM function, we pass CEERAN0 a seed. Suppose we set this seed to 1 (the same as our COBOL RANDOM function), and run it on the same z/OS system. We get:
​LE Number 1:  .44270715510598717E 00
This number is different to the COBOL RANDOM function output, even though we use the same seed. Different programming languages and other functions often use different methods to create a pseudo random number sequence. The Enterprise COBOL RANDOM function uses something called the “rectangular distribution,” while CEERAN0 uses the “multiplicative congruential method.” We won’t go into the mathematical details, but you can find information about them on the web.

Either way, CEERAN0 and other language-based random functions all use some kind of mathematical process to get the pseudo random sequence. So, the chances are that if you run the same code, with the same seed, on different z/OS systems or CECs, you’ll get the same sequence of numbers.

A Simple Random Number

This is great if you want to create a sequence of random numbers. However, what if we just want a single random number?

To do this, we need a relatively random seed. Yes, we need a random number to get a random number. But that random seed doesn’t need to be all that random: “kind of” random works. For example, we could ask the user to specify a seed in a parameter file. Or we could generate a seed from a number that regularly changes. One idea is to pass a time, or datetime value as the seed.

In our COBOL code, we could do this as follows:
* Get current time                                       
      Move Function Current-Date(9:8) to WS-HHMMSSTT     
* Get random number between 0 and 1, using time as seed  
      Compute WS-RNDNUM-L1 = Function RANDOM(WS-HHMMSSTT)

Running this code twice in the same COBOL program produces something like:
Number 1:  .70938615347695823E 00
Number 2:  .15209330858294540E 00
The same tactic can be used for any function that outputs a pseudo-random sequence.

Anyone used to working with UNIX will now be jumping up and down, and screaming “/dev/random!” And they have a point. The device /dev/random or /dev/urandom have been used for a long time by UNIX applications to return random numbers. As z/OS is also a POSIX certified OS, you’d expect it to have something similar, and you won’t be disappointed.

/dev/random and /dev/urandom are interesting. Traditionally, they calculated random data from environmental noise from device drivers. /dev/random may make a program wait if there is not enough “noise” collected; /dev/urandom will do the best it can. Today, OSes can choose their own method.

/dev/random and friends produce random data, not random numbers. The following command uses the z/OS UNIX shell command od to show 8 bytes from /dev/urandom as hexadecimal numbers:
DZS:/: >od -An -tx -N8 /dev/urandom 
          4FBB64B4        6422427F  
                                    
DZS:/: >                           
 So, we’ll need to do some work in our program if we want to convert this to a number. 

The good news is that /dev/random and friends are not pseudo-random number generators: they produce ‘true’ random data. Every time we issue this od command, we’ll get different output. 

ICSF Features

Before the z14 mainframe, z/OS used ICSF for /dev/random. ICSF is the z/OS component dealing with encryption and decryption, and it provides its own APIs to return a true random number. Here’s an example of a COBOL program doing exactly that:
  01 WS-RC              Pic 9(8) Comp.                        
  01 WS-REAS            Pic 9(8) Comp.                        
  01 WS-EXITL           Pic 9(8) Comp Value 0.                
  01 WS-EXIT            Pic X(4).                             
  01 WS-RANDNUM         Pic X(8).                             
  01 WS-FORM            Pic X(8) Value 'RANDOM  '.           
                                                             
  Procedure Division.                                         
                                                             
       Call 'CSNBRNG' Using WS-RC,                            
                            WS-REAS,                          
                            WS-EXITL,                         
                            WS-EXIT,                          
                            WS-FORM,                          
                            WS-RANDNUM                        
                                                             

       Display 'Number: ' WS-RANDNUM                          
This pretty much does what /dev/random did: return 8 bytes of true random data. The CSNERNG program is similar, but allows the caller to determine the length of data to return. According to the z/OS manuals, ICSF uses a “time variant input with a very low probability of recycling” as the basis for calculating this random data.

These ICSF programs (and /dev/random when it relies on ICSF) have a couple of issues. Firstly, the CSF started task must be up and running to use these any other ICSF functions. Secondly, the program will need RACF authority to call the CSNBRNG/CSNERNG programs. Finally, ICSF needs certain cryptographic features for the IBM Z mainframe enabled.

Advantages of the z14

In 2017, IBM announced the new z14 mainframe. Together with z/OS 2.3, this heralded the beginning of pervasive encryption: encrypting all data everywhere. The z14 included some interesting new features, including “Message-Security-Assist Extension 7”. Before the z14, the PERFORM RANDOM NUMBER OPERATION (PRNO) instruction could be used to generate pseudo-random sequences at a fraction of the CPU cost of doing it in software. The z14 added a true random number generator option for PRNO.

Although high level languages like COBOL and C can’t directly use this feature, it can be used indirectly. For example, from the z14, /dev/random and /dev/urandom now use this feature: they no longer need ICSF.

Which Random Number Method to Use

So, there’s probably more ways of generating a random number on z/OS than you thought. But which should you use? There are two main factors to consider:
  1. Security. Most users will be satisfied with the pseudo-random numbers offered by languages like COBOL and C. Most of these use fullword (31-bit) arithmetic: a lot of possible numbers. C functions like lrand48() and drand48() use 48-bits: more possible numbers, so perhaps a little more secure. Those needing true random numbers will look towards the ICSF functions, , and the PRNO instruction: these return as many bytes of random data as you need.
 
  1. Performance and CPU usage. Traditionally, generating pseudo-random numbers has been faster than true random numbers. So, COBOL’s RANDOM statement is usually much faster than ICSF CSNBRNG. However, IBM Z hardware features have made true random number generation much faster: particularly those introduced with the z14. So, today using /dev/random is about as fast as pseudo-random number functions and features.   
Webinars

Stay on top of all things tech!
View upcoming & on-demand webinars →