Random Generator Completeness Test
There are many different tests to assess the quality of a random-number generator. The Diehard suite of tests from Prof. G. Marsaglia was the first in wide-spread use, and a lot more tests have been developed since. Presented here is a test for completeness. Linear Pseudorandom Number Generators have an exact amount of numbers they generate before the return to their starting point (wrap around). A good generator generates all possible numbers exactly once before wrapping around. During the development of specific variants of such generators it is useful to be able to do such a check.
The method used here is brute-force. It needs a decent CPU and at least 512 MB of continuous memory to run in a reasonable time. But a Raspberry Pi has enough memory and is fast enough to run a check in reasonable time.
It functions thus:
clear the 512 MB bit-array reset the random generator under test 4294967296 0 DO get a random number from generator under test set bit with generated number as index in the bit-array LOOP reset popcount array 134217728 0 DO get next 32b value from array do a popcount of this number add 1 to the corresponding counter in the popcount array LOOP show the popcount array in a clear way
Reporting the results:
The report is the only 'smart' part of this program. It does a popcount of each of the 134217728 32 bits numbers in the array. As a second step it shows an overview of the totals of each of the 33 possible populations counts.
The first report shows a 32 bit LPNG which is complete: there are 134217727 population bit counts with a value of 32, and 1 popcount with a value of 31. This is exactly as expected. These generators have a period of 232-1, the 0 is never generated; so there is 1 popcount with the value 31 and the rest with value 32.
0=> 0 1=> 0 2=> 0 3=> 0 4=> 0 5=> 0 6=> 0 7=> 0 8=> 0 9=> 0 10=> 0 11=> 0 12=> 0 13=> 0 14=> 0 15=> 0 16=> 0 17=> 0 18=> 0 19=> 0 20=> 0 21=> 0 22=> 0 23=> 0 24=> 0 25=> 0 26=> 0 27=> 0 28=> 0 29=> 0 30=> 0 31=> 1 32=> 134217727
To raise complexity of a random-generator, it is possible to multiply the output with a constant factor. For an example see the random generator of MeCrisp-quintus. You cannot just use any multiplication factor. The following table shows the effect of using a wrong multiplication factor. In this case the generator only generates 25% of the possible numbers (but these 4 times in a complete cycle). The factor used in the MeCrisp generator is obviously correct!
0=> 0 1=> 0 2=> 0 3=> 0 4=> 0 5=> 0 6=> 0 7=> 0 8=> 134217728 9=> 0 10=> 0 11=> 0 12=> 0 13=> 0 14=> 0 15=> 0 16=> 0 17=> 0 18=> 0 19=> 0 20=> 0 21=> 0 22=> 0 23=> 0 24=> 0 25=> 0 26=> 0 27=> 0 28=> 0 29=> 0 30=> 0 31=> 0 32=> 0
And this is how the popcounts look with a high quality 32 bit generator with a 256 bit domain ( xoxhiro256 from D. Blackman and S. Vigna). The normal distribution can almost be felt…
0=> 0 1=> 0 2=> 0 3=> 0 4=> 0 5=> 6 6=> 52 7=> 237 8=> 1406 9=> 6292 10=> 24445 11=> 84668 12=> 254086 13=> 672015 14=> 1567053 15=> 3230909 16=> 5898377 17=> 9542529 18=> 13661188 19=> 17303565 20=> 19326867 21=> 18960579 22=> 16292092 23=> 12174977 24=> 7844886 25=> 4313613 26=> 1994819 27=> 763860 28=> 233027 29=> 55479 30=> 9614 31=> 1031 32=> 56
Runtime and performance observations
The runtime on a Raspberry 3b+ with wabiForth for this check is around 24 minutes (and 5 sec to generate the report). The limiting factor is not the speed of the CPU, but the speed of the memory-bus and cache. This routine is the most cache-inefficient routine possible. In >99,99% of cases setting a bit in the array requires the reading and writing off a complete 64 byte cache line. Setting a bit 4294967296 times takes a while… The resulting memory bandwidth is 380 MB/s. Well under the 1100 MB/s available to wabiForth on a Raspberry pi 3+. But taking into account that the cache-system is stressed to the max for all aspects, this is respectable and leaves only little room for improvement.