--- a/README.markdown Mon Mar 20 15:24:02 2017 +0000
+++ b/README.markdown Thu Mar 23 14:08:19 2017 +0000
@@ -1,6 +1,8 @@
`cl-pcg` is a [permuted congruential generator][pcg] implementation in pure
Common Lisp.
+PCGs are **not** cryptographically secure. If you need that, look elsewhere.
+
[pcg]: http://www.pcg-random.org/
* **License:** MIT
--- a/docs/02-usage.markdown Mon Mar 20 15:24:02 2017 +0000
+++ b/docs/02-usage.markdown Thu Mar 23 14:08:19 2017 +0000
@@ -4,6 +4,8 @@
`cl-pcg` is a [permuted congruential generator][pcg] implementation in pure
Common Lisp. It provides a high-level API and a low-level API.
+PCGs are **not** cryptographically secure. If you need that, look elsewhere.
+
[pcg]: http://www.pcg-random.org/
[TOC]
@@ -63,8 +65,18 @@
(pcg-random *gen* 15 28) ; => [15, 28)
(pcg-random *gen* 15 28 t) ; => [15, 28] <- inclusive endpoint!
-`pcg-random` can also generate `single-float`s if `bound` and `max` are given as
-`single-float`s.
+`inclusive?` is treated as a generalized boolean, so you can write `(pcg-random
+gen -10 10 :inclusive)` if you feel it reads better.
+
+`pcg-random` can also generate `single-float`s if `bound` and/or `max` are given
+as `single-float`s:
+
+ :::lisp
+ (defparameter *gen* (make-pcg))
+
+ (pcg-random *gen* 10.0) ; => [0.0, 10.0]
+ (pcg-random *gen* 0 10.0) ; => [0.0, 10.0]
+ (pcg-random *gen* -1.0 1.0) ; => [-1.0, 1.0]
### The Global Generator
@@ -114,3 +126,9 @@
The low-level API assumes you will pass in arguments of the correct type. If
you fuck this up, all bets are off. Read the code to figure out exactly what
you need to pass in (or just use the high-level API like a sane person).
+
+Limitations
+-----------
+
+You can only generate 32-bit integers, and only single floats. This will change
+whenever I get around to fixing things up.
--- a/docs/03-reference.markdown Mon Mar 20 15:24:02 2017 +0000
+++ b/docs/03-reference.markdown Thu Mar 23 14:08:19 2017 +0000
@@ -65,6 +65,21 @@
(PCG-RANDOM PCG BOUND &OPTIONAL MAX INCLUSIVE?)
+Generate and return a random number in the specified interval.
+
+ If `max` is omitted the interval will be `[0, bound)`.
+
+ If `max` is given the interval will be `[bound, max)`.
+
+ If `inclusive?` is given the interval will be `[bound, max]`.
+
+ If either of `bound` or `max` are floats, the result will be a float.
+ Otherwise the result will be an integer.
+
+ As a side effect, the state of `pcg` will be advanced.
+
+
+
### `PCG-RANDOM%` (function)
(PCG-RANDOM% PCG)
--- a/docs/index.markdown Mon Mar 20 15:24:02 2017 +0000
+++ b/docs/index.markdown Thu Mar 23 14:08:19 2017 +0000
@@ -1,6 +1,8 @@
`cl-pcg` is a [permuted congruential generator][pcg] implementation in pure
Common Lisp.
+PCGs are **not** cryptographically secure. If you need that, look elsewhere.
+
[pcg]: http://www.pcg-random.org/
* **License:** MIT
Binary file src/pcg.fasl has changed
--- a/src/pcg.lisp Mon Mar 20 15:24:02 2017 +0000
+++ b/src/pcg.lisp Thu Mar 23 14:08:19 2017 +0000
@@ -279,52 +279,57 @@
pcg-designator))
-(defun pcg-random-integer (pcg bound &optional max inclusive?)
+(defun pcg-random-integer (pcg min max)
"Return a random integer.
- If `max` is omitted the result will be in the interval `[0, bound)`.
+ As a side effect, the state of `pcg` will be advanced.
- If `max` is given the result will be in the interval `[bound, max)`.
+ "
+ (check-types pcg pcg-designator
+ min integer
+ max integer)
+ (+ min (pcg-random-bounded% (resolve-pcg pcg)
+ (+ (- max min)))))
- If `inclusive?` is true the result will be in the interval `[bound, max]`.
+(defun pcg-random-float (pcg min max)
+ "Return a random `single-float`.
As a side effect, the state of `pcg` will be advanced.
"
(check-types pcg pcg-designator
- bound u32
- max (or null u32))
- (let ((pcg (resolve-pcg pcg)))
- (if (null max)
- (pcg-random-bounded% pcg bound)
- (+ bound (pcg-random-bounded% pcg (+ (- max bound)
- (if inclusive? 1 0)))))))
+ min single-float
+ max single-float)
+ (+ min (* (- max min)
+ (pcg-random-float% (resolve-pcg pcg)))))
+
+(defun pcg-random (pcg bound &optional max inclusive?)
+ "Generate and return a random number in the specified interval.
-(defun pcg-random-float (pcg &optional bound max)
- "Return a random `single-float`.
+ If `max` is omitted the interval will be `[0, bound)`.
+
+ If `max` is given the interval will be `[bound, max)`.
- If `bound` is omitted the result will be in the interval `[0, 1)`.
+ If `inclusive?` is given the interval will be `[bound, max]`.
- If `max` is omitted the result will be in the interval `[0, bound)`.
-
- If `max` is given the result will be in the interval `[bound, max)`.
+ If either of `bound` or `max` are floats, the result will be a float.
+ Otherwise the result will be an integer.
As a side effect, the state of `pcg` will be advanced.
"
- (check-types pcg pcg-designator
- bound (or null single-float)
- max (or null single-float))
- (let ((f (pcg-random-float% (resolve-pcg pcg))))
- (cond
- ((null bound) f)
- ((null max) (* bound f))
- (t (+ bound (* (- max bound) f))))))
-
-(defun pcg-random (pcg bound &optional max inclusive?)
- (etypecase bound
- (integer (pcg-random-integer pcg bound max inclusive?))
- (single-float (pcg-random-float pcg bound max))))
+ (let* ((float? (or (floatp bound)
+ (floatp max)))
+ (result-type (if float? 'single-float 'integer))
+ (delta (if (and inclusive? (not float?)) 1 0))
+ (min (coerce (if (null max) 0 bound) result-type))
+ (max (coerce (+ (if (null max) bound max) delta) result-type)))
+ (assert (< min max) ()
+ "Invalid interval for generating a random number: [~S, ~S~A"
+ min max (if inclusive? "]" ")"))
+ (case result-type
+ (integer (pcg-random-integer pcg min max))
+ (single-float (pcg-random-float pcg min max)))))
(defun pcg-advance (pcg steps)
--- a/test/tests.lisp Mon Mar 20 15:24:02 2017 +0000
+++ b/test/tests.lisp Thu Mar 23 14:08:19 2017 +0000
@@ -16,16 +16,83 @@
;;;; Tests --------------------------------------------------------------------
+(defparameter *iterations* 200)
+(defparameter *run-length* 5000)
+
+
+(define-test seeding
+ (loop :repeat *iterations*
+ :for seed = (random (expt 2 64))
+ :for stream = (random (expt 2 32))
+ :for a = (make-pcg :seed seed :stream-id stream)
+ :for b = (make-pcg :seed seed :stream-id stream)
+ :for a-vals = (gimme 200 (pcg-random a (1- (expt 2 32))))
+ :for b-vals = (gimme 200 (pcg-random b (1- (expt 2 32))))
+ :do (is (equal a-vals b-vals))))
+
(define-test rewind
- (let ((g (make-pcg))
- (a nil)
- (b nil))
- (setf a (gimme 20 (pcg-random g 50000)))
- (pcg-rewind g 20) ; Rewind to start
- (setf b (gimme 20 (pcg-random g 50000)))
- (is (equal a b))
- (setf a (gimme 10 (pcg-random g 50000)))
- (pcg-rewind g 10) ; Rewind back to midpoint
- (setf b (gimme 10 (pcg-random g 50000)))
- (is (equal a b))
- ))
+ (loop :repeat *iterations*
+ :do (let ((g (make-pcg))
+ (a nil)
+ (b nil)
+ (bound (1+ (random 50000))))
+ (setf a (gimme 20 (pcg-random g bound)))
+ (pcg-rewind g 20) ; Rewind to start
+ (setf b (gimme 20 (pcg-random g bound)))
+ (is (equal a b))
+
+ (setf a (gimme 10 (pcg-random g bound)))
+ (pcg-rewind g 10) ; Rewind back to midpoint
+ (setf b (gimme 10 (pcg-random g bound)))
+ (is (equal a b)))))
+
+(define-test upper-bound
+ (loop :repeat *iterations*
+ :for bound = (1+ (random 50))
+ :for gen = (make-pcg)
+ :for vals = (gimme *run-length* (pcg-random gen bound))
+ :do (is (every (lambda (n) (<= 0 n (1- bound)))
+ vals))))
+
+(define-test bounds-exclusive
+ (loop :repeat *iterations*
+ :for min = (- (random 50) 25)
+ :for max = (+ min 1 (random 10))
+ :for gen = (make-pcg)
+ :for vals = (gimme *run-length* (pcg-random gen min max))
+ :do (is (every (lambda (n) (<= min n (1- max)))
+ vals))))
+
+(define-test bounds-inclusive
+ (loop :repeat *iterations*
+ :for min = (- (random 50) 25)
+ :for max = (+ min 1 (random 10))
+ :for gen = (make-pcg)
+ :for vals = (gimme *run-length* (pcg-random gen min max t))
+ :do (is (every (lambda (n) (<= min n max))
+ vals))))
+
+(define-test bounds-floats
+ (loop :repeat *iterations*
+ :for min = (- (random 50.0) 25)
+ :for max = (+ min 1 (random 10.0))
+ :for gen = (make-pcg)
+ :for vals = (gimme *run-length* (pcg-random gen min max))
+ :do (is (every (lambda (n) (<= min n max))
+ vals))))
+
+(define-test degenerate-bounds
+ (loop :repeat *iterations*
+ :for gen = (make-pcg)
+ :for vals = (gimme *run-length* (pcg-random gen 1))
+ :do (is (every #'zerop vals))))
+
+(define-test type-coercion
+ (let ((gen (make-pcg)))
+ (is (floatp (pcg-random gen 1.0)))
+ (is (floatp (pcg-random gen 0.0 1.0)))
+ (is (floatp (pcg-random gen 0 1.0)))
+ (is (floatp (pcg-random gen 0.0 1)))
+ (is (integerp (pcg-random gen 10)))
+ (is (integerp (pcg-random gen 0 10)))
+ (is (integerp (pcg-random gen 0 10 t)))))