Search code examples
randomclojure

How do include the upper limit of `rand` in Clojure?


In Clojure's rand function, the lower limit 0 is included, but the upper limit is not. How to define a rand function equivalent which includes the upper limit?


Solution

  • Edit (simple explanation): Because rand generates a random double between 0 and 1 by generating an integer and dividing my another integer, you can implement a version with inclusive upper bound as

    (defn rand-with-nbits [nbits]
      (let [m (bit-shift-left 1 nbits)]
        (/ (double (rand-int m))
           (double (dec m)))))
    
    (apply max (repeatedly 1000 #(rand-with-nbits 3)))
    ;; => 1.0
    

    In the implementation, 53 bits are used, that is (rand-with-nbits 53).

    Longer answer:

    Take a look at the source code of rand. It internally calls Math.random, which in turn calls Random.nextDouble, that has the following implementation:

    private static final double DOUBLE_UNIT = 0x1.0p-53; // 1.0 / (1L << 53)
    ...
    public double nextDouble() {
        return (((long)(next(26)) << 27) + next(27)) * DOUBLE_UNIT;
    }
    

    The above code essentially produces a random integer between 0 and 2^53-1 and divides it by by 2^53. To get an inclusive upper bound, you would instead have to divide it by 2^53-1. The above code calls the next method which is protected, so to make our own tweaked implementation, we use proxy:

    (defn make-inclusive-rand [lbits rbits]
      (let [denom (double
                   (dec           ; <-- `dec` makes upper bound inclusive
                    (bit-shift-left
                     1 (+ lbits rbits))))
            g (proxy [java.util.Random] []
                (nextDouble []
                  (/ (+ (bit-shift-left
                         (proxy-super next lbits) rbits)
                        (proxy-super next rbits))
                     denom)))]
        (fn [] (.nextDouble g))))
    
    (def my-rand (make-inclusive-rand 26 27))
    

    It is unlikely that you will ever generate the upper bound:

    (apply max (repeatedly 10000000 my-rand))
    ;; => 0.9999999980774417
    

    However, if the underlying integers are generated with fewer bits, you will see that it works:

    (def rand4 (make-inclusive-rand 2 2))
    
    (apply max (repeatedly 100 rand4))
    ;; => 1.0
    

    By the way, the above implementation is not thread-safe.