;;;; generation of random bignums ;;;; ;;;; The implementation assumes that the random chunk size is either ;;;; equal to the word size or equal to half the word size. ;;;; This software is part of the SBCL system. See the README file for ;;;; more information. ;;;; ;;;; This software is derived from the CMU CL system, which was ;;;; written at Carnegie Mellon University and released into the ;;;; public domain. The software is in the public domain and is ;;;; provided with absolutely no warranty. See the COPYING and CREDITS ;;;; files for more information. (in-package "SB!BIGNUM") ;;; Return T if the least significant N-BITS bits of BIGNUM are all ;;; zero, else NIL. If the integer-length of BIGNUM is less than N-BITS, ;;; the result is NIL, too. (declaim (inline bignum-lower-bits-zero-p)) (defun bignum-lower-bits-zero-p (bignum n-bits) (declare (type bignum-type bignum) (type bit-index n-bits)) (multiple-value-bind (n-full-digits n-bits-partial-digit) (floor n-bits digit-size) (declare (type bignum-index n-full-digits)) (when (> (%bignum-length bignum) n-full-digits) (dotimes (index n-full-digits) (declare (type bignum-index index)) (unless (zerop (%bignum-ref bignum index)) (return-from bignum-lower-bits-zero-p nil))) (zerop (logand (1- (ash 1 n-bits-partial-digit)) (%bignum-ref bignum n-full-digits)))))) ;;; Return a nonnegative integer of DIGIT-SIZE many pseudo random bits. (declaim (inline random-bignum-digit)) (defun random-bignum-digit (state) (if (= n-random-chunk-bits digit-size) (random-chunk state) (big-random-chunk state))) ;;; Return a nonnegative integer of N-BITS many pseudo random bits. ;;; N-BITS must be nonnegative and less than DIGIT-SIZE. (declaim (inline random-bignum-partial-digit)) (defun random-bignum-partial-digit (n-bits state) (declare (type (integer 0 #.(1- digit-size)) n-bits) (type random-state state)) (logand (1- (ash 1 n-bits)) (if (<= n-bits n-random-chunk-bits) (random-chunk state) (big-random-chunk state)))) ;;; Create a (nonnegative) bignum by concatenating RANDOM-CHUNK and ;;; BIT-COUNT many pseudo random bits, normalise and return it. ;;; RANDOM-CHUNK must fit into a bignum digit. (declaim (inline concatenate-random-bignum)) (defun concatenate-random-bignum (random-chunk bit-count state) (declare (type bignum-element-type random-chunk) (type (integer 0 #.sb!xc:most-positive-fixnum) bit-count) (type random-state state)) (let* ((n-total-bits (+ 1 n-random-chunk-bits bit-count)) ; sign bit (length (ceiling n-total-bits digit-size)) (bignum (%allocate-bignum length))) (multiple-value-bind (n-random-digits n-random-bits) (floor bit-count digit-size) (declare (type bignum-index n-random-digits)) (dotimes (index n-random-digits) (setf (%bignum-ref bignum index) (random-bignum-digit state))) (if (zerop n-random-bits) (setf (%bignum-ref bignum n-random-digits) random-chunk) (progn (setf (%bignum-ref bignum n-random-digits) (%logior (random-bignum-partial-digit n-random-bits state) (%ashl random-chunk n-random-bits))) (let ((shift (- digit-size n-random-bits))) (when (< shift n-random-chunk-bits) (setf (%bignum-ref bignum (1+ n-random-digits)) (%digit-logical-shift-right random-chunk shift))))))) (%normalize-bignum bignum length))) ;;; Create and return a (nonnegative) bignum of N-BITS many pseudo ;;; random bits. The result is normalised, so may be a fixnum, too. (declaim (inline make-random-bignum)) (defun make-random-bignum (n-bits state) (declare (type (and fixnum (integer 0)) n-bits) (type random-state state)) (let* ((n-total-bits (1+ n-bits)) ; sign bit (length (ceiling n-total-bits digit-size)) (bignum (%allocate-bignum length))) (declare (type bignum-index length)) (multiple-value-bind (n-digits n-bits-partial-digit) (floor n-bits digit-size) (declare (type bignum-index n-digits)) (dotimes (index n-digits) (setf (%bignum-ref bignum index) (random-bignum-digit state))) (unless (zerop n-bits-partial-digit) (setf (%bignum-ref bignum n-digits) (random-bignum-partial-digit n-bits-partial-digit state)))) (%normalize-bignum bignum length))) ;;; Create and return a pseudo random bignum less than ARG. The result ;;; is normalised, so may be a fixnum, too. We try to keep the number of ;;; times RANDOM-CHUNK is called and the amount of storage consed to a ;;; minimum. ;;; Four cases are differentiated: ;;; * If ARG is a power of two and only one random chunk is needed to ;;; supply a sufficient number of bits, a chunk is generated and ;;; shifted to get the correct number of bits. This only conses if the ;;; result is indeed a bignum. This case can only occur if the size of ;;; the random chunks is equal to the word size. ;;; * If ARG is a power of two and multiple chunks are needed, we call ;;; MAKE-RANDOM-BIGNUM. Here a bignum is always consed even if it ;;; happens to normalize to a fixnum, which can't be avoided. ;;; * If ARG is not a power of two but one random chunk suffices an ;;; accept-reject loop is used. Each time through the loop a chunk is ;;; generated and shifted to get the correct number of bits. This only ;;; conses if the final accepted result is indeed a bignum. This case ;;; too can only occur if the size of the random chunks is equal to the ;;; word size. ;;; * If ARG is not a power of two and multiple chunks are needed an ;;; accept-reject loop is used that detects rejection early by ;;; starting the generation with a random chunk aligned to the most ;;; significant bits of ARG. If the random value is larger than the ;;; corresponding chunk of ARG we don't need to generate the full ;;; amount of random bits but can retry immediately. If the random ;;; value is smaller than the ARG chunk we know already that the ;;; result will be accepted independently of what the remaining random ;;; bits will be, so we generate them and return. Only in the rare ;;; case that the random value and the ARG chunk are equal we need to ;;; generate and compare the complete random number and risk to reject ;;; it. (defun %random-bignum (arg state) (declare (type (integer #.(1+ sb!xc:most-positive-fixnum)) arg) (type random-state state) (inline bignum-lower-bits-zero-p)) (let ((n-bits (bignum-integer-length arg))) (declare (type (integer #.sb!vm:n-fixnum-bits) n-bits)) ;; Don't use (ZEROP (LOGAND ARG (1- ARG))) to test if ARG is a power ;; of two as that would cons. (cond ((bignum-lower-bits-zero-p arg (1- n-bits)) ;; ARG is a power of two. We need one bit less than its ;; INTEGER-LENGTH. Not using (DECF N-BITS) here allows the ;; compiler to make optimal use of the type declaration for ;; N-BITS above. (let ((n-bits (1- n-bits))) (if (<= n-bits n-random-chunk-bits) (%digit-logical-shift-right (random-chunk state) (- n-random-chunk-bits n-bits)) (make-random-bignum n-bits state)))) ((<= n-bits n-random-chunk-bits) (let ((shift (- n-random-chunk-bits n-bits)) (arg (%bignum-ref arg 0))) (loop (let ((bits (%digit-logical-shift-right (random-chunk state) shift))) (when (< bits arg) (return bits)))))) (t ;; ARG is not a power of two and we need more than one random ;; chunk. (let* ((shift (- n-bits n-random-chunk-bits)) (arg-first-chunk (ldb (byte n-random-chunk-bits shift) arg))) (loop (let ((random-chunk (random-chunk state))) ;; If the random value is larger than the corresponding ;; chunk from the most significant bits of ARG we can ;; retry immediately; no need to generate the remaining ;; random bits. (unless (> random-chunk arg-first-chunk) ;; We need to generate the complete random number. (let ((bits (concatenate-random-bignum random-chunk shift state))) ;; While the second comparison below subsumes the ;; first, the first is faster and will nearly ;; always be true, so it's worth it to try it ;; first. (when (or (< random-chunk arg-first-chunk) (< bits arg)) (return bits)))))))))))