;;;; The five randomness functions, pinned. ;;;; ;;;; Three properties are asserted here and each is asserted as a *number* off ;;;; a fixed seed rather than as a range, because a range would still pass if ;;;; the derivation changed under it: ;;;; ;;;; 1. A seed fixes the sequence. The same five calls after the same seed ;;;; print the same five lines. ;;;; 2. A call that answers a number costs exactly one draw. Each block seeds, ;;;; makes one call, and prints the *next* rand-int: all five print the ;;;; same number, which is only true if each consumed one step of the ;;;; generator. The one call that is not a draw is the one that is not a ;;;; number — a range with nothing in it answers lo and leaves the ;;;; generator where it was, which is asserted under 3. ;;;; 3. The ranges are half-open and never divide by zero: [5, 5) and a ;;;; reversed range answer lo, a range of one always answers lo, a loop ;;;; over [0, 4) never produces 4, and the whole of i64 is a range like any ;;;; other — its span only exists by wrapping. ;;;; ;;;; And rand-bool over a thousand draws, which is the only one of the five ;;;; whose distribution a single number can check. (defn main [] i32 ;; 1. Reproducibility: two identical blocks, and therefore two identical ;; pairs of lines in the expected output. (dotimes [round 2] (rand-seed 20260921) (print (rand-int)) (print " ") (print (rand)) (print " ") (print (rand-bool)) (print " ") (print (rand-int-range -10 10)) (print " ") (print (rand-float-range 2.0 3.0)) (println "")) ;; 2. One draw each. The first line is the second draw after seed 1, and ;; every line under it is that same draw reached through a different one of ;; the five. (rand-seed 1) (rand-int) (println (rand-int)) (rand-seed 1) (rand) (println (rand-int)) (rand-seed 1) (rand-bool) (println (rand-int)) (rand-seed 1) (rand-int-range 0 100) (println (rand-int)) (rand-seed 1) (rand-float-range 0.0 1.0) (println (rand-int)) ;; 3. The boundaries. An empty range, a reversed one, and a range of one ;; answer lo without dividing; a range at the extremes of i64 still lands ;; inside it. (print (rand-int-range 5 5)) (print " ") (print (rand-int-range 5 -5)) (print " ") (let [ones 0] (dotimes [i 20] (when (= (rand-int-range 7 8) 7) (set ones (+ ones 1)))) (print ones)) (println "") ;; And an empty range is not a draw: two of them either side of a rand-int ;; leave it the number it would have been on its own. Seeded twice so the ;; two lines are the same number, which is the whole assertion. (rand-seed 1) (println (rand-int)) (rand-seed 1) (rand-int-range 5 5) (rand-int-range 9 -9) (println (rand-int)) ;; And the widest range there is. hi - lo is 2^64 - 1 here and only reaches ;; that by wrapping, which is what the u64 it is read as makes right: the ;; answer has to be an i64 and not a number outside one. (rand-seed 3) (println (rand-int-range -9223372036854775808 9223372036854775807)) ;; hi is excluded, and lo is reachable: over 2000 draws on [0, 4) the top ;; value seen is 3 and the bottom is 0. (let [top 0 bottom 3 fours 0] (dotimes [i 2000] (let [v (i32 (rand-int-range 0 4))] (set top (max top v)) (set bottom (min bottom v)) (when (= v 4) (set fours (+ fours 1))))) (print top) (print " ") (print bottom) (print " ") (print fours) (println "")) ;; The float range is half-open for the same reason: rand never reaches 1.0, ;; so nothing here reaches hi. (let [over 0] (dotimes [i 2000] (let [v (rand-float-range -1.0 1.0)] (when (or (>= v 1.0) (< v -1.0)) (set over (+ over 1))))) (print over) (println "")) ;; rand-bool over a thousand draws. The count is pinned, and a fair coin puts ;; it near 500 — a generator that had lost a bit would sit at 0 or 1000, and ;; one biased enough to matter would miss the band. (rand-seed 99) (let [heads 0] (dotimes [i 1000] (when (rand-bool) (set heads (+ heads 1)))) (print heads) (print " ") (print (and (> heads 450) (< heads 550))) (println "")) 0)