RNG147
March 28, 2017
Let’s begin with a simple implementation of the random number generator; we’ll change this in a few moments:
(define rand (let ((seed 0.3141592654)) (lambda args (set! seed (if (pair? args) (car args) (let ((x (* seed 147.0))) (- x (floor x))))) seed)))
Our random number generator returns the next random number in the sequence; if it is given an argument, it resets the seed:
> (rand) 0.18141201379999927 > (rand) 0.667566028599893 > (rand) 0.13220620418427131 > (rand 0.5) 0.5 > (rand) 0.5 > (rand) 0.5
Our random number works fine initially, but setting the seed to 0.5 causes it to loop repeatedly, as 147.0 * 0.5 = 73.5 and the fractional part of the calculation never changes. We can fix that by ensuring that simple seeds become complex:
(define rand (let ((seed 0.3141592654)) (lambda args (set! seed (if (pair? args) (sin (car args)) (let ((x (* seed 147.0))) (- x (floor x))))) seed)))
Taking the sine of the seed breaks the pattern:
> (rand) 0.18141201379999927 > (rand) 0.667566028599893 > (rand) 0.13220620418427131 > (rand 0.5) 0.479425538604203 > (rand) 0.47555417481784445 > (rand) 0.9064636982231349
That looks better. We’ll use that version of the random number generator for the remainder of the exercise. It is convenient to have a function that returns a list of random numbers; randlist
returns a list of n random numbers, after optionally setting the seed of the first random number in the list (which is returned in reverse order, not that it matters):
(define (randlist n . args) (when (pair? args) (rand (car args))) (do ((n n (- n 1)) (rs (list) (cons (rand) rs))) ((zero? n) rs))))
We perform two tests to determine the suitability of the random number generator. The first test counts the frequency of the first k digits of the fraction:
(define (freq n k) (uniq-c = (sort < (map inexact->exact (map floor (map (lambda (x) (* x (expt 10 k))) (randlist n)))))))
Here are results for k = 1 and k = 2:
> (freq 10000 1) ((0 . 983) (1 . 1067) (2 . 1019) (3 . 1040) (4 . 1043) (5 . 965) (6 . 994) (7 . 969) (8 . 928) (9 . 992)) > (freq 100000 2) ((0 . 981) (1 . 1040) (2 . 965) (3 . 1011) (4 . 930) (5 . 967) (6 . 1007) (7 . 987) (8 . 962) (9 . 966) (10 . 1036) (11 . 1028) (12 . 1026) (13 . 986) (14 . 958) (15 . 960) (16 . 999) (17 . 1048) (18 . 1067) (19 . 968) (20 . 1046) (21 . 930) (22 . 1014) (23 . 980) (24 . 952) (25 . 968) (26 . 1036) (27 . 960) (28 . 969) (29 . 1002) (30 . 998) (31 . 1006) (32 . 1001) (33 . 1074) (34 . 958) (35 . 1016) (36 . 988) (37 . 968) (38 . 994) (39 . 993) (40 . 981) (41 . 1094) (42 . 985) (43 . 999) (44 . 1010) (45 . 1003) (46 . 1016) (47 . 1017) (48 . 1008) (49 . 988) (50 . 942) (51 . 971) (52 . 1030) (53 . 1034) (54 . 1042) (55 . 997) (56 . 959) (57 . 1038) (58 . 1033) (59 . 1009) (60 . 1001) (61 . 1066) (62 . 998) (63 . 920) (64 . 983) (65 . 952) (66 . 982) (67 . 1014) (68 . 1003) (69 . 1021) (70 . 933) (71 . 1057) (72 . 1043) (73 . 1063) (74 . 1034) (75 . 1044) (76 . 966) (77 . 1039) (78 . 1034) (79 . 1020) (80 . 984) (81 . 970) (82 . 981) (83 . 1022) (84 . 1057) (85 . 983) (86 . 1024) (87 . 951) (88 . 973) (89 . 1009) (90 . 1017) (91 . 970) (92 . 991) (93 . 1020) (94 . 1003) (95 . 945) (96 . 988) (97 . 996) (98 . 1021) (99 . 1021))
We expect all of the counts to be about 1000, and they are. The second test looks at whether the sequence increases or decreases at each step:
(define (dir n) (let loop ((rs (randlist n)) (up 0) (down 0)) (cond ((null? (cdr rs)) (list up (- n up down 1) down)) ((< (car rs) (cadr rs)) (loop (cdr rs) (+ up 1) down)) ((< (cadr rs) (car rs)) (loop (cdr rs) up (+ down 1))) (else (loop (cdr rs) up down)))))
The test shows increases and decreases are about the same, and there are no ties:
> (dir 100000) (50036 0 49963)
There are many other tests that random number generators are typically required to pass, but, at first glance, RNG147 looks suitable for basic use in simulations that require random floating-point numbers, and is simple to implement and fast to operate. You can run the program at http://ideone.com/AUVQPw.
In python 3
Your sine trick only moves the problem elsewhere; try seeding with pi/6 (arcsin of 1/2).
Solution in Haskell, using the Stream package for explicitly infinite lists:
Here’s another Haskell version. With an integer argument, n, it prints the first n random numbers. With no arguments it converts the stream of random Doubles into a stream of 32-bit unsigned integers. The purpose is to feed that binary data to the dieharder program, which evaluates its randomness. We don’t make use of all the bits in the Double; it’s just an easy way of getting the data to dieharder in a form that it likes.
Here’s the result of feeding the output to dieharder. I won’t try to interpret the results, here. (The -a argument tells it to run all the tests it has; -g 200 says that stdin is a series of 32-bit unsigned integers.) The summary is that 82 tests passed, 27 failed and 5 were weak.
[…] built several random number generators: [1], [2], [3], [4], [5], [6], [7], [8], [9] (I didn’t realize it was so many until I went back and looked). In today’s exercise we […]