**PSEUDO-RANDOM NUMBER GENERATORS**

**(REPORT II)**

A report submitted in partial fulfilment of the requirements for the degree of Bachelor of Science

Z. O’Connell (950786535)

Department of Information Systems and Computing,

Brunel University

June 1999

# Table of Contents

Table of Contents 2

1 Background 4

1.1 Definition and Scope 4

1.2 Objectives 4

2 Overall design 5

2.1 Structure 5

2.2 Files 6

2.3 Testing 7

3 Testers 8

3.1 Introduction 8

3.2 API 8

3.3 Library routines 8

3.4 Tests implemented 9

3.5 Unimplemented tests 12

3.6 General observations and conclusion 12

4 Generators 13

4.1 Introduction 13

4.2 API 13

4.3 Library routines 14

4.4 Implemented generators 15

4.5 Unimplemented Generators 19

4.6 General observations and conclusion 19

5 Front-ends 21

5.1 Tester 21

5.2 test-static 21

6 Conclusions 22

References 23

Appendix A – Evaluation 24

A.1 Project Overview 24

A.2 The Approach 24

A.3 Possible Future Work In The Area 24

Appendix B – Tables of results 25

B.1 Empirical test results 25

B.2 Speed test results 26

Appendix C – Source Code 27

# 1 Background

## 1.1 Definition and Scope

There are many known algorithms for generating numbers which are, or at least appear to be, random.

There are also a large number of tests to check that what is produced by such generators does closely

resemble a random stream. There is not, however, a currently freely available suite of generators and

testing routines allowing the user to eaisly define new generators and testers for experimental

purposes. This project implements the framework for such a system.

It would be impossible in the timescale to study every possible generator and test procedure available.

The project does however implement the most common of these and it should be very simple to add

new libraries as required.

## 1.2 Objectives

There are two main objectives:

To produce a portable set of pseudo-random number generators that can be easily used as

part of any application in a variety of programming languages

To test the random number generators for speed and randomness

The main consideration when writing these generators is portability and usefulness. Although in many

cases the generators are coded in such a way that they are slower than they could be trying to speed

up the generators would make the code less readable. The main suite of random number

generators/testers currently available, Diehard, [9 ] consists mostly of highly-optimized FORTRAN code

which has then been automatically converted to C. Even with a detailed knowledge of how the

generators and testers work it is usually impossible to figure out how the code works or adapt it for

other uses.

To achieve the objectives, we can break the problem down into three tasks which need to be

considered:

The generator library and a well-defined API (Application Programmers Interface)

The testing library and a well-defined API

A front-end that can be used to test the generators

# 2 Overall design

## 2.1 Structure

Each generator or tester is presented as a stand-alone file that, with the exception of combination

generators, does not rely on other generators. These libraries are dynamically loadable at run time or

can be compiled into a program, allowing the choice of generator to be specified by the programmer or

end-user as desired. There is also a central library of functions used by all the libraries to avoid

excessive duplication of code.

The code is all written in C. There are several reasons for this choice of language. Firstly, it is fast.

Most other languages are harder to optimize and perform bounds checking on arrays and variables

which, while normally desirable, will slow the generators down quite significantly. Secondly, it is

portable. C code can be compiled on almost any computer currently in existence. This allows most of

the generators, except combination ones which rely on dlopen() as discussed below, to be compiled on

virtually any platform. Finally, C code is easy to build into libraries and most programming languages

allow C code to be called from within them.

The development platform is Unix, (Linux primarily, also tested on Solaris 2.5.1) however as the code is

written with portability in mind and it should be easy to compile on other systems with only minor

modifications. There are two notable exceptions to this – dlopen() and gmp. Dlopen() is used to load

libraries dynamically and is only available on some systems. It is used primarily by the front-end tester

but also by some of the “combination” generators. The combination generators could be modified to link

in their component generators statically although this would take quite a bit of work to allow all the

generators to have “alternative” function names when used as component generators. Combination

generators perform poorly both in terms of speed and randomness however so this doesn’t seem

worthwhile.

The ICG generator uses the GMP GNU multi=precision math library for doing inverse multiplication.

Although this library will compile on the majority of systems it is not generally installed as standard.

Both the generators and testers work on bit-streams rather than floating point numbers. Although most

older random number generator systems generate floating point numbers they are harder to analyze

and reliance on floating point math would slow down the generators. It would be trivial, if required, to

convert the output from this API to a floating point number. Care should be taken to ensure that the

granularity of the numbers obtained from a generator is sufficient however – clearly taking a single bit at

a time is going to produce poor results if three decimal places of randomness is required.

## 2.2 Files

Makefile Instructions for building the programs (See below)

*.c Source code

*.so Compiled dynamic-link libraries

librnd_name.* Generator “name” (See section 4.4)

libtest_name.* Tester “name” (See section 3.4)

random.h Global function definitions, included by every generator.

librandom.* Useful routines used by many generators and testers. (See sections 3.3 and

4.3)

tester Simple testing program that loads in generators and testers dynamically. (See

section 5.1)

test-static Statically linked example showing the display tester and the Mersenne Twister

generator. (See section 5.2)

random.dat Random source used for the file generator and as a seed by other generators.

(See below)

Each generator/tester is a separate source (.c) file and compiles into a separate object (.o) or library

(.so) file.

### 2.2.1 Makefile

The Makefile is used to compile the code by simply typing make on a Unix system. make -k should

normally be used to force make build all the libraries even if some fail as the ICG generator will only

compile if the gmp library is present. There are only two things that need to be changed in the Makefile

in the normal course of events. The first is the CCOPTIONS line. Normal options would be -O6 -Wall,

indicating full optimization and all compiler warnings. Other useful options are -DVERBOSE to turn on

extra output in generators and testers and -g instead of -O6 to enable debugging symbols in the output

files for use with debuggers such as gdb. -DQUIET can be used to suppress all output from testers.

This is for use when testers might want to be included in a larger program although isn’t the most

common case and therefor isn’t the default option.

If desired, the libraries compiled into test-static can be easily changed here, demonstrating how easy it

would be to incorporate a new generator as part of a program using this API. Debuggers don’t handle

dynamic linking particularly well so in most cases it is eaiser to link a specific library statically as part of

test-static for debugging.

Typing “make clean” will delete all the compiled libraries and executable.

The variable LIB_GCC is a workaround for an apparent bug in the gcc and egcs compilers. For some

reason, under certain circumstances involving dynamic linking not all the correct functions for unsigned

long long math are included in librandom.so. Adding the libgcc library specifically resolves this problem.

### 2.2.2 Random.dat

For a “true” random source and as a seed for some generators, this file was downloaded from a

hardware source on the Internet. [10] The web site doesn’t allow large downloads in one go, so around

a hundred separate files were downloaded and concatenated together to produce one larger file.

## 2.3 Testing

Although the generators are tested by the testers, the testers themselves need to be validated to

ensure they give the expected results. To do this, we can use generators known to be “perfect”, such

as cryptographic generators, which should not fail any statistical test. There is a small probability that a

truly random source will produce non-random output however this possibility is very small given the

amount of data being generated and this problem was not encountered.

There are also certain expected results for most tests, as almost every paper discussing a testing

mechanism gives several tests and the results obtained by the author. However,there are many ways of

implementing certain tests and some generators do fail tests they have been shown to pass in other

situations.

# 3 Testers

## 3.1 Introduction

The testers are presented as separate libraries which can either be linked in with a program at compile-time or, if supported on the specific platform, dynamically selected and loaded at run-time. The output is

a boolean pass or fail indication for a particular generator, although all the testers produce additional

human-readable text to showing what results were produced. It is not expected that testers will be

called automatically by programs using the libraries, however in same cases this may be desirable.

Some applications may wish to produce a different set of random numbers each time without saving the

state of the generators between each run. However, some “seed” values for generators may produce

poor output. A good tester, such as Maurer, can be quickly applied to a particular generator with a

certain seed value to check if it produces sane output.

## 3.2 API

There are only two functions required for the tester modules:

int rantest_test(void *state, getbits function)

char *rantest_info_text(void)

Rantest_test takes a pointer to a generators state array and a pointer to a getbits function and

produces a single boolean result to indicate pass or fail. All of the testers produce some sort of

diagnostic output however giving actual p-values where appropriate, and if VERBOSE is defined in the

Makefile they will usually give details of the internal counters. Verbose output is usually unlabelled

however and only really interesting for debugging purposes.

Rantest_info_text simply copies a descriptive string into scratch and returns a pointer to that. It is

implemented this way, rather than by a static text string in the generator, so that extra dynamic

information on the test library could be output, as with the generators, although this is never actually

used.

## 3.3 Library routines

There are two routines in librandom.c called by testing routines. These are:

double ChiSqPN(unsigned long df, double x)

double KolSmirP(unsigned long df, double x)

ChiSqPN takes degrees of freedom and a value and calculates the p-value for the Chi-squared

distribution from x with df degrees of freedom. The code is adapted from poorly documented Javascript

by Ritter [8] as no other readily-implementable formulae or algorithms were available. This routine

works by normalising x and then passing that to NormalP which actually calculates the p-value for the

normal distribution using a fairly simple formulae.

KolSmirP is also adapted from code by Ritter but is far more complicated and difficult to understand.

Similarly to ChiSqPN it calculates the p-value for the Kolmogorov-Smirnov distribution from x with df

degrees of freedom. Unfortunately, the Kolmogorov-Smirnov distribution has even less information

available about it than the Chi-squared distribution and the source of the underlying algorithm isn’t

known.

## 3.4 Tests implemented

### 3.4.1 Display tester

This “display” test simply prints the first 100,000 16-bit numbers from the generator being “tested”. This

is for manually checking the output of a generator is sane, (No leading zeros/all zeros output) doesn’t

have an incredibly short period and for manually checking that a generator is iterating according to the

correct formula.

### 3.4.2 Speed tester

The speed test measures how fast the generator can produce 2,000,000 16 bit numbers and the results

are presented in Appendix A. Three platforms were measured, Intel, Sun and Alpha, although not all

platforms were directly available so only Intel results are given in full. The test is slightly unfair in that

generators that produce results in multiples of 16 bits will perform better as rangen_generic_getbits is

faster when bits are being requested in multiples of the generator bits per iteration. The reflects how the

generators would probably be used in practice however. There are few surprises here, the generators

perform as would generally be expected and in roughly the same order on all platforms. The file

generator appears slightly faster than all zeros on Intel because it loads a 64-bit value into STATE->output every iteration whereas the zero generator only loads 32 bits a time.

The tester actually measures the CPU time elapsed for the process rather than the actual time elapsed

so even on a fairly heavily loaded machine results will be accurate. On faster generators, however, the

granularity of the system clock seems to cause a problem and affect results by about 5%. The

compiler used can make a more significant difference and can affect results by as much as 50%. On all

platforms, the tests used the gcc compiler. On the sun platform however the sunpro compiler was

found to be between 30% and 300% faster than gcc for various generators.

The speed of a generator can heavily affect the choice of generator for an application – a generator

such as the linux /dev/random, whilst highly random and unpredictable, is far too slow if tens of millions

of random numbers are required.

The speed test always passes a generator.

### 3.4.3 Chi-squared

The simple statistical chi-squared test fails a surprising number of generators given that it is

independent of ordering. We test ten million 16-bit values however, which will tend to cause any

generator with a period lower than a few million to fail badly. Almost every generator which fails another

test also fails Chi-squared although this is more likely to be coincidence that poor generators also have

shorter periods that are detected by Chi-squared. Generators with short periods fail because slight

deviations from the expected distribution are exaggerated as they loop and give the same values again.

Chi-squared is a two-tailed test so a low value is as bad as a high one – LCG 2 fails as the values are

too close to the expected distribution to be random. A generator passes this test if 0.005 < p < 0.995 - i.e. it passes at the two-tailed 1% level.

Because this generator is “two-tailed” it detects sequences that are “too random” – i.e. that fir the

expected distribution improbably well – as well as those that are not random enough. LCG 2 exhibits

this property – it gives a Chi-squared value of zero.

### 3.4.4 Kolmogorov-Smirnov

The KS test is really just a weaker version of the Chi-squared test and fails most of the generators that

Chi-squared picks up. Each result is one-tailed – I.e. it only fails if too high not too low – but two values

are produced and the test fails if either are too high. This tester is weaker than Chi-squared primarily

because calculating the p-value for ten million degrees of freedom is too time-consuming

computationally whereas the million degrees of freedom used are much faster. This test and other tests

based on it passes the test if p < 0.99 - i.e. it passes at the one-tailed 1% level.

### 3.4.5 Gap test

The gap test measures the distributions of gaps between occurrences of a number fits the expected

distribution. This test actually measures using chi-squared the distribution of gaps between each

possible 8-bit number individually and then performs a Kolmogorov-Smirnov test on the results.

Because of this the test proves to be very powerful – it picks up flaws in generators that every other test

passes. LFSRs and FCSRs failing is a surprise, although looking at debug output by uncommenting the

commented out code lines in the tester shows that the two linear feedback generators are failing due to

extremely long gaps between certain numbers. (This produces a **lot** of output)

### 3.4.6 Poker test

The poker tests measures occurrences of the same digit in a four-digit hexadecimal number generated

by the generators. Because we are doing this test on bit streams rather than floating point numbers,

any non-randomness here should also be picked up by the Chi-squared test and this is indeed the

case.

### 3.4.7 Runs up/runs down

This test proved very difficult to implement as Knuth’s description for calculating the matrices required

for this test isn’t clear. The matrixes, a and b, are hard-coded into the program however as they do not

change when other parameters of the tester change. This tests fails weak generators as expected and

is reasonably powerful. One surprise is that it fails the FCSR generator, which no other tester other

than the gap test fails. The two results this tester produces are Chi-squared vlues for runs up and runs

down.

### 3.4.8 Runs above/below the mean

This is a very weak test and fails only the poorest of generators. This generator produces four outputs –

a pair of KS results for each of runs above and below the mean. This test doesn’t fail the zero

generator because of a peculiarity in the formulae which will only occur when the entire input is above

or below the mean.

### 3.4.9 Maurer’s Universal Statistical bit test

Maurer’s test almost lives up to his claim of being able to detect any flaw in a generator detectable by

other means although fails to spot flaws spotted by the gap tester. Unfortunately, his original paper lists

an incorrect expected value when calculated on 16-bit numbers, so the test was conducted only on 8-bit. Maurer’s paper lists the expected value as 15.1670.001 whereas experimental evidence strongly

indicates a value closer to 15.159. The version of the code implemented is a hand-converted C version

of Maurer’s Pascal code [5] and performs flawlessly on 8-bit numbers so the error appears to be in the

expected values provided. The error appears to be mathematical, not typographical, as the same value

is repeated elsewhere within the paper. Unfortunately, calculating the value to the required precision for

testing a million random numbers (Maurer’s code only tests 20,000 8-bit numbers) requires 200,000

iterations of the formula which to achieve in a reasonable amount of time requires more computing

power than readily available. (A bc script to calculate it had not completed after over two days of

running on a PPro200 when the machine was shut down)

### 3.4.10 File tester

This tester simply writes 2 million bytes of the output of the generator as binary data to a file called

output.dat. This file can then either be renamed for use as a seed/input for other generators or used for

some other purpose.

## 3.5 Unimplemented tests

### 3.5.1 Serial correlation

This test isn’t implemented as no exact version of it appears to exist. Knuth gives a version but only

gives conjectured formulae for the expected values which appear not to fit well with actual empirical

results. This test is not included in modern random number packages and appears to have fallen into

disuse, possibly because it measures similar properties to other non-statistical tests such as the run

and gap tests.

3.5.2 Marsagla’s “Stringent” tests

Without further analysis, Marsagla’s tests are not easy to implement. The information supplied with

Diehard [9] is insufficient to easily figure out the algorithm being used for some tests. Unfortunatly, as

has been mentioned previously, the code in Diehard is totally impenetrable and gives no clue as to the

authors intentions.

## 3.6 General observations and conclusion

Implementing some of the testers, particularly the more common “Knuth” types, proved quite difficult

due to conflicting and inaccurate literature. For example, Banks et al. Give the number of degrees of

freedom for a gap test as the sample size whereas in practice it is actually the number of discrete

values the output can have. (256 for an 8-bit output) The error in Maurer’s paper, mentioned previously,

is another example of this problem. Peer-review of papers in this area appears poor, possibly because

most current work concentrates on cryptographic generators and there is currently little interest in non-cryptographic implementations.

When testing new generators, it would seem sensible to test using Maurer’s test or the Gap test first as

they are the most powerful tests which are more likely to reject a generator than any other if it is

flawed. The generator should then be tested against the other, less powerful, tests if desired.

# 4 Generators

## 4.1 Introduction

The generators use a similar API to the testers. The internals of the generator are normally kept hidden

from the calling program so that generators can be changed and the program relinked with no

modification. There are routines for changing the internal variables of a generator however and these

can be used by programs with a greater knowledge of the internals of a specific generator or by a user

to select different values for the generator, either for testing purposes or because specific values might

be more appropriate for a certain applications. Programs can get values from a generator by means of

a routine which extracts a specified number of bits at a time. There is also an information routine in

each generator which will output a textual description of a generator.

## 4.2 API

There are many more routines used by the generators than there are for testers. These are:

char *rangen_info_text(void *state);

unsigned int rangen_info_statesize(void);

void *rangen_create(void);

void rangen_init(void *);

void rangen_iterate(void *state);

char *rangen_info_getparametername(unsigned int number);

unsigned long long rangen_info_getparametervalue(unsigned int parameter void *state);

void rangen_init_setparameter(unsigned int parameter, unsigned long long value, void *state);

unsigned long long rangen_getbits(void *state, unsigned int bits);

In addition, the state structure should begin as follows:

struct state_struct {

unsigned long long output;

unsigned int outputbitsavailable; };

Rangen_info_text takes a pointer to generator state and returns a pointer to a string giving details of

the generator. If VERBOSE is defined in the Makefile this can include details of the current generator

settings. Rangen_info_statesize returns the current size of the state array which can be used to make a

copy of the generator or, if desired, save it’s current state to disk. Generators that cannot be copied or

saved to disk in this way return 0.

Rangen_create allocates memory for a generator state and returns a pointer. It will also usually fill in

some reasonable default values for the generator. Rangen_init should be called once all parameters

have been set using the state pointer to initialize any arrays and values before rangen_getbits is called

for the first time. If parameters are changed and rangen_init is not called before the next call to

rangen_getbits, behavior is undefined. This will make some generators crash as checks for this are not

performed in the interests of speed.

Rangen_info_getparametername returns a pointer to a string describing parameter number. If number

is higher than the maximum parameter number, NULL is returned. If the first three letters of a

parameter are “Alg” (Short for algorithm) the value required is a pointer to a generator function and the

next parameter is a pointer to generator state. Rangen_info_getparametervalue takes a state pointer in

addition to a parameter number and returns the value of that parameter. Similarly,

rangen_info_setparametervalue can be used to set a value. If parameter zero is available it should be a

value appropriate for “seeding” the generator. No guarantees are made as to how well any particular

seed value will perform however.

There is no sanity checking performed on values selected in this way and unpredictable things (Like

crashes) could happen if invalid values are entered.

Rangen_getbits is the most-used routine and returns an appropriate number of bits from the generator.

In all the generators implemented her this is simply a call to the library routine rangen_generic_getbits

discussed below. Rangen_iterate is intended primarily for use by librandom for putting a new value into

STATE->output. STATE->output always contains the current generator output and STATE->outputbits

the number of bits of random number left in STATE->output.

Throughout the generators, STATE is used as a shorthand for ((struct state_struct *)state). This is

because the state is always passed as a void pointer so that programs without knowledge of the

generator internals can pass it around and thus it requires explicit typing every time it is referenced.

## 4.3 Library routines

Two library routines are used by generators. These are:

int countbits(unsigned long long);

unsigned long long rangen_generic_getbits(void *state, unsigned int bits, void (*iterate)(void *));

Countbits is used simply to count the position of the left-most one in a binary number and is used by

generators such as Von Neumann and LCGs either as part of the algorithm or to calculate the number

of random bits generated per iteration.

Rangen_generic_getbits takes a state pointer, which must start in the way described above, a number

of bits desired and a pointer to an iteration function and will iterate an appropriate number of times to

produce the desired number of bits. Every generator implemented here uses this routine.

## 4.4 Implemented generators

### 4.4.1 “Reference” generators

There are three generators which are implemented to allow testing of the testers. These are Linux, File

and Zero – any valid test should pass the first two and fail the zero generator.

The “Linux” generator uses the crypto-random generator present in the Linux operating system kernel

which uses internal hardware sources of the machine to generate an unpredictable bit stream. The

generator actually uses the non-cryptographic file /dev/urandom as this is much faster than the

cryptographically secure generator /dev/random. It still uses cryptographic functions and hardware

sources so is indeed random.

“File” reads random data from a file “random.dat”. The file used in testing was generated from

http://www.random.org/, which gets data from a hardware random source. The reason this generator

fails the Chi-squared test is because the file in use was only 2Mb and twenty million bytes (10,000,000

16-bit numbers) were tested.

Although primarily intended to test the testers, these two generators could be used in real-world

applications if relatively small amount of randomness is required. There are faster and more convenient

generators available however.

“Zero”, as the name implies, simply generates a stream of zeros.

The Zero and File generators also give an indication of the maximum possible speed of a generator –

I.e. how much is lost in function call overhead compared to actual computational time. The file

generator is marginally faster on Intel because it works on 64 bits values whereas the zero generator

only generates one 32-bit per call to rangen_iterate. (The file generator was reading data off a remote

filesystem on the Sun machine, hence the lower speed)

None of these generators take any parameters and their state cannot be saved.

(Rangen_info_stateside returns zero)

### 4.4.2 C Library generators

The “Rand” generator tests the properties of the random number generator random() in the system C

library. Rand 1 is results from a Linux machine running glibc2, which performs admirably and is also

reasonably fast. The source code for the library doesn’t give references as to the source of the

generator but it appears to be a “polynomial” type of some description. On a glibc2 machine, this

generator is certainly more than acceptable for randomness although slower than other generators.

Rand 2 are results from a Solaris 2.5.1 machine and passes all the tests except Chi-squared.

Documentation claims this is a “non-linear additive feedback” generator with a period in the order of

2^31 which if true indicates that this failure isn’t in fact due to a small period in the generator but to a

major flaw in it’s implementation.

This generator takes no parameters.

### 4.4.3 Von Neumann

The Von Neumann generator as implemented performs poorly in almost all tests. The version used is a

“Binary” version – taking the middle binary digits and squaring them – rather than the original decimal

version Von Neumann proposed. A hexadecimal version could also easily be implemented, although

would offer no obvious benefits over the binary version. The original “decimal” version of this generator

would be much slower and also offer no improvement over the binary version.

This generator takes one parameter – number zero is the current internal state of the generator and can

be used to “seed” the generator. The default seed value used is one that has been observed to

perform reasonably well in the Maurer test, which is the most powerful test available, compared to other

seed values. Although this is a relatively fast generator on most platforms it’s failure in over half the

tests makes it unsuitable for use.

### 4.4.4 Linear Congruential Generator

The values used in LCG 1 are those suggested by Knuth and those in LCG 2 are the defaults compiled

into the generator as suggested by Ritter. Neither set performs particularly well, failing even the Chi-squared test suggesting that the period of these generators is quite low. Although other values may

perform better the generator as implemented is reliant to m being a power of two as we are producing

random bit streams rather than random floating point numbers. Converting from values of m other than

powers of two into a random bit stream is very difficult to do without destroying the randomness of the

generator. LCGs are the most common generators in current usage however because although slower

for large batches of numbers they are easily implemented and fast if only a few values are needed.

This generator takes four parameters. Parameter zero is the internal state of the generator and can be

used as a seed. The first, second and third parameters are m, c and a respectively. M is the modules

of the generator, c the value added each cycle (Zero for pure Multiplicative Congruential Generators)

and a the multiplication value.

### 4.4.5 Additive Congruential Generators

ACGs perform incredibly well given their simplicity and speed. The initial table used is based on the first

few values in the file used for the “File” generator with a default “lag” of 97. This is the only generator

actually implemented other than Mersenne Twisters that actually passes all the tests and achieve over

a million numbers per second on a PPro200.

This generator takes two parameters. Parameter one is m, the modules and number two is k, the “lag”.

As this generator cannot be usefully seeded in it’s current incarnation there is no parameter zero.

### 4.4.6 Inversive Congruential Generators

ICGs perform disappointingly, failing every single test, even the very weak runs above/below the mean

tests. Looking at the output it is clear this generator has a very low period – a few hundred – making it

totally useless. There appear to be some suggestions that this is a good generator however I can find

no source that states this with any certainly. Due to it’s use of inverse multiplication it is also very slow,

making it unsuitable for general use. This generator is further hindered by it’s use of the external library

“gmp” – the GNU Multi-Precision math library – for inversive multiplication, which is probably not present

on all systems.

Although it might be possible to speed up this generator and increase it’s period, there seems little

reason to do so. As there is no “hidden” state in this generator it’s maximum period is it’s modules as

with LCGs and there appear to be no benefits of this generator over LCGs despite being slower so the

effort does not seem to be worthwhile.

The parameters of the ICG are the same as for the LCG – zero is the state then one to three are m, c

and a.

### 4.4.7 L’Ecuyer

The L’Ecuyer combination generator also performs poorly. L’Ecuyer doesn’t give a convincing argument

as to why the output should be uniformly distributed and looking at the verbose output of Chi-squared is

clear there is bias towards particularly high or low results. The bias is only significant for a reasonably

large sample size, greater than 10,000, which is probably why L’Ecuyers original testing of his

generator did not spot this flaw.

The API as defined can not handle an arbitrary number of sub-generators, as L’Ecuyer uses, so the

configuration of this generator is static and there are no parameters.

### 4.4.8 Algorithm M

Algorithm M performs comparably to the LCGs – It is based on the same generator as LCG 1 for it’s

actual output, shuffled by another identical LCG generator. Although the output of the generator in

terms of it’s ordering is “more random” than a plain LCG, the LCGs used pass most of the ordering-dependant However, LCGs do fail a Chi-squared test and shuffling the output does not help here as

Algorithm M also fails the Chi-squared test.

Algorithm M has quite a few parameters. Firstly, powerk defines how big a “shuffle box” is required –

the implementation limits this to powers of two so, for example, setting powerk to 8 gives a box of size

256. Parameter two is the number of bits obtained from the generator each cycle. Parameters three

through six are the algorithm and state for the two generators X (Number source) and Y. (Shuffle

source)

### 4.4.9 Linear Feedback Shift Register

This generator passes most of the tests well. Rather surprisingly it fails the gap test, which examination

of verbose output reveals is due to very high gaps between certain reoccurances of the same number.

Unfortunately, because they have to work bit-by-bit it is rather slow to implement in software and are

nearly and order of magnitude slower than Mersenne Twisters and ACGs which perform just as well.

The default settings for the tap bits on this generator is taken from Schneider [4].

There are fiver parameters for this generator. Parameter zero is, as always, the current state and can

be used to seed the generator. (Although a seed of zero will produce a zero output) Parameters one to

four are the tap bit locations, from 0 to 63.

### 4.4.10 Feedback with Carry Shift Register generators

As with LFSRs, FCSRs suffer from the same failure in the gap test and are even slower. They offer no

obvious advantage over FCSRs and take the same parameters.

### 4.4.10 Mersenne Twisters

The code for the Mersenne Twisters is adapted from the original code by Matsumoto and Nishimura

[12] with no major modifications. Despite it’s reliance on a relatively large number of operations per

cycle mathematical operations it is very fast as it generates over eight thousand random bits each time.

Rangen_iterate doesn’t return all generated bits every time it is called but breaks them down into more

manageable 32-bit chunks – the real iterate routine is called as required. This generator passes all the

tests easily without any problems and because of it’s speed seems the generator of choice where

anything more than a few random numbers are required. “Seeding” this generator would be difficult as

the initial state is relatively large (Over 8 thousand random bits) although this could be overcome simply

by taking an offset into an existing random source, such as the random.dat currently used to seed the

generator.

Mersenne Twisters have no configurable parameters.

## 4.5 Unimplemented Generators

### 4.5.1 Wichmann & Hill Combined

This generator, presented in [2] only produces floating point results and there is insufficient information

in the paper to determine the granularity of the generator which would be required to convert the output

to a bit-stream efficiently.

### 4.5.2 Polynomial Congruential

As seen by the results of the Rand test, Polynomal Congruential generators are both reasonably fast

and quite random. Unfortunately, although this syle of generator is used by glibc2 and also mentioned

by Knuth insufficient information is available to attempt a decent implementation.

## 4.6 General observations and conclusion

Implementing the generators proved a much eaiser task than the testers. To construct a good generator

far more theory is required and invariably all the generators include either working code or an algorithm

that can be used to quickly produce working code.

It is clear from the table of results in Appendix B that Mersenne Twisters and ACGs are by far the

generators of choice. They are both very fast and fail none of the tests presented here.

# 5 Front-ends

## 5.1 Tester

The first and most useful front-end, “tester”, takes the name of a generator and tester library and runs

them. This program shows how to dynamically load tester libraries on a Unix system that supports

dlopen(). It is necessary to set the environment variable LD_LIBRARY_PATH to the directory holding

the libraries or install the libraries in the system libraries directory so that the dynamic linker can find

them.

Tester is called with two arguments, firstly the full filename of a generator library and second the full

filename of a tester library, for example:

tester librnd_mersenne.so libtest_display.so

(Assuming “tester” is in the current PATH)

If VERBOSE is defined when compiling a library then tester first outputs a short text description of each

library obtained using the info_text functions.

If there are any configurable parameters for the generator then tester will prompt for them one by one,

showing default values. The default values can be accepted by just pressing enter without entering any

data.Tester cannot handle “Alg” type parameters and ignores them.

## 5.2 test-static

The second tester, test-static, shows how simple it is to include a generator (And tester) in a program.

The libraries included in the static executable can easily be changed by modifying the

STATIC_LIBRARIES variable in the Makefile. Default settings produce a completely static executable,

i.e. one that requires no support libraries other than the standard C libraries. Substituting the .so files

for .o gives a dynamically linked version, where the library files are required to run the program, but

with no modifications to the source. This latter method is the preferred method of linking in the random

number libraries as if a generic name, such as librndgen.so, is used the generator can be changed

merely by replacing the library file with no need to change the source code.

# 6 Conclusions

The results here are not entirely surprising. As noted in the first report, both Mersenne Twisters and

ACGs were expected to do reasonably well. The failure of LFSRs is a surprise however, as these seem

to be regarded as reasonably proficient generators and although slow in software are often used in

hardware implementations. The failure of combined generators is also somewhat surprising, although

recent research work has moved away from this and into totally new generators.

There is little to be said about the testers as they all performed as expected. It is worth reiterating the

poor quality of work on random number testing however, in the hope that someone may eventually

produce a work that actually includes algorithms for implementing tests quickly and accurately.

Algorithms for this are very thin on the ground and often had to be derived from ambiguous statements

and the ambiguities resolved by experimentation.

# References

[1] Knuth, D.E., 1969, The Art of Computer Programming, Volume 2 – Seminumerical Algorithms,

Addison-Wesley

[2] Wichmann, B.A., Hill, I.D., 1982, A Pseudo-Random Number Generator, NPL Report DITC 6/82,

National Physical Laboratory.

[3] L’Ecuyer, P., 1988, Efficient and Portable Combined Random Number Generators, pp.742-749, 774,

Communications of the ACM Volume 31, Number 6.

[4] Schneier, B., 1996, Applied Cryptography, John Wiley & Sons.

[5] Maurer, U.M., 1992, A Universal Statistical Test for Random Bit Generators, pp.89-105, Journal of

Cryptology, Vol.5, No. 2.

[6] Banks, J., Carson 2nd, J., Nelson, B., 1996, Discrete-Event System Simulation, Prentice-Hall.

[7] Fishman, G., 1973, Concepts and Methods in Discrete Event Digital Simulation, John Wiley & Sons.

[8] Ritter, T., Ciphers by Ritter, http://www.io.com/~ritter/

[9] DIEHARD, http://stat.fsu.edu/~geo/diehard.html

[10] Haahr, M., http://www.random.org/

[11] Marsaglia, G., 1984, A current view of Random Number Generators, Computer Science and

Statistics: The Proceedings of the 16th Symposium on the Interface, Atlanta, Elsevier Press.

[12] M. Matsumoto and T. Nishimura, 1998, Mersenne Twister: A 623-dimensionally equidistributed

uniform pseudorandom number generator, ACM Transactions on Modeling and Computer Simulation,

Vol. 8, No. 1, pp.3-30.

[13] Sullivan, J., 1998, Numerical Analysis & Associated Fields Resource Guide,

http://net.indra.com/~sullivan/q10.html

# Appendix A – Evaluation

## A.1 Project Overview

Initial work was on structuring the API in such a way that it was flexible enough to cope with most

generators and testers although still specific enough to allow generators to be interchanged easily in a

program. Once a rough outline had been produced the “reference” generators and display and speed

tester were written as a “proof of concept” and the API refined as a result of this. Constructing the API

and resolving issues surrounding the basic framework took around a quater of the time, primarily

because constructing dynamic-link libraries and particularly using the dlopen() call causes problems not

encountered in normal programming. Debugging also proved a problem – although most generators

can be linked statically combination generators such as L’Ecuyer rely on dlopen() to work which makes

using standard debuggers such as gdb tricky.

Only around ten percent of the time was spent implementing generators as this proved a relatively easy

task given the wide availability of algorithms to perform the most common generation functions. Writing

the testers and running tests took over half the time taken. This was due primarily to the difficulties in

implementing testing libraries that have been discussed at length previously. The remaining time was

spent writing the report.

## A.2 The Approach

There is little to be said about the approach that has not already been covered in the body of the

report. No major assumptions were required for implementation and the reasoning behind the structure

of the project and choice of language has already been covered in section 2.1.

## A.3 Possible Future Work In The Area

This project could be extended quite considerably to encompass far more generators and testers than

are currently implemented. Trying to implement even a significant portion of the available generators

would be quite a significant work however as there are a large number of generators and an almost

infinite number of possible parameters for those generators.

# Appendix B – Tables of results

## B.1 Empirical test results

Chi-sq | KS | Gap | Poker | Runs | Mean | Maurer | ||

Linux | 0.362208 | 0.510559 0.675486 |
0.712583 0.000526 |
0.478271 0.428924 |
0.359596
0.874975 |
0.247679 0.393694 |
0.184395 0.458329 |
7.184869 |

File | 1.000000 | 0.738718 0.948190 |
0.935290 0.001834 |
0.011070 0.011181 |
0.497735 0.132207 |
0.130925 0.047258 |
0.174363 0.272003 |
7.184507 |

Zero | 1.000000 | 1.000000 0.000000 |
0.000000 1.000000 |
1.000000 1.000000 |
1.000000 1.000000 |
0.000000 0.000000 |
0.000000 0.000000 |
0.000001 |

Rand 1 | 0.715177 | 0.820229 0.018466 |
0.480549 0.096585 |
0.414513 0.399850 |
0.138942 0.473569 |
0.252494 0.710310 |
0.138706 0.399274 |
7.184058 |

Rand 2 | 1.000000 | 0.753250 0.382823 |
0.071074 0.162250 |
0.131359
0.109035 |
0.228733 0.801380 |
0.473434 0.175626 |
0.588748 0.818538 |
7.185135 |

Von N. | 1.000000 | 1.000000 1.000000 |
0.000000 1.000000 |
0.996104
0.972135 |
1.000000 1.000000 |
0.859635 0.757239 |
0.931238 0.753966 |
7.196248 |

LCG 1 | 1.000000 | 0.247861 0.804789 |
0.000000 1.000000 |
0.154877
0.061890 |
0.230819 0.159677 |
0.215360 0.324168 |
0.477696 0.260543 |
7.198858 |

LCG 2 | 0.000000 | 0.033461 0.952883 |
0.000000 1.000000 |
0.032041
0.083639 |
0.014468 0.013934 |
0.885685 0.897066 |
0.914775 0.975280 |
7.233509 |

ACG | 0.191041 | 0.771454 0.167969 |
0.376785 0.270558 |
0.022058
0.093272 |
0.292254 0.498318 |
0.180289 0.403473 |
0.231975 0.144920 |
7.183310 |

ICG | 1.000000 | 1.000000 1.000000 |
0.999999 0.998766 |
0.999999
0.998766 |
1.000000 1.000000 |
0.928292 0.739950 |
0.999918 1.000000 |
7.180711 |

L’Ecuyer | 1.000000 | 1.000000 1.000000 |
0.000000 1.000000 |
0.852401
0.554670 |
1.000000 1.000000 |
0.118549 0.250390 |
0.180989 0.411692 |
7.169784 |

Alg. M | 1.000000 | 0.033915 0.811274 |
0.000000 1.000000 |
0.149044
0.061221 |
0.010887 0.498318 |
0.295884 0.919160 |
0.324374 0.510433 |
7.196667 |

LFSR | 0.505779 | 0.657751 0.104576 |
0.000000 1.000000 |
0.033919
0.012633 |
0.160175 0.184342 |
0.138039 0.101322 |
0.291267 0.243762 |
7.184588 |

FCSR | 0.084193 | 0.517227 0.511065 |
0.000000 1.000000 |
0.033539
0.056973 |
0.821765 0.818449 |
0.580329 0.835615 |
0.836863 0.335689 |
7.183938 |

Mer.Tw. | 0.806905 | 0.642702 0.057305 |
0.532591 0.116994 |
0.097397
0.119505 |
0.424862 0.960693 |
0.717156 0.727823 |
0.245693 0.405926 |
7.183670 |

Red: Failure at the 1% level Yellow: Failure at the 5% level Green: Pass

## B.2 Speed test results

Intel PPro200 | Sun Ultra 5/10
(Sun4u 299MHz) |
Alpha 21164 533MHz | |

file | 1851 | 2251 | |

zero | 1770 | 2483 | 6736 |

Mersenne | 1470 | 2125 | |

Von Neumann | 1418 | 727 | 5120 |

ACG | 1190 | 1309 | 4808 |

Rand | 1190 | 1636 | |

LCG | 1058 | 920 | 4367 |

L’Ecuyer | 794 | 336 | 1295 |

Algorithm M | 374 | 304 | 1694 |

LFSR | 160 | 176 | |

FCSR | 141 | 171 | |

ICG | 54 | ||

Linux | 34 |

All values are thousands of 16-bit values per second.