#lang racket

(require math/flonum)

(provide random-exponential random-laplace
         random-flonum random-single-flonum
         random-integer->random-exact-rational
         random-integer->random-flonum
         random-integer->random-single-flonum
         random-integer->random-real)

;; Returns a rational flonum from an exponential distribution
;; About half the numbers from this distribution are < scale, and half are > scale
(define (random-exponential [scale 1.0])
  (let loop ()
    (define r (random))
    ;; appx. probability 1.11e-16 this loops:
    (cond [(= r 0)  (loop)]
          [else  (exact->inexact (* (- (log (random))) scale))])))

;; Randomly signs a flonum from random-exponential
(define (random-laplace [scale 1.0])
  (define r (random-exponential scale))
  (if ((random) . < . 0.5) r (- r)))

;; Returns an integer from Uniform(0,2^64-1)
(define (random-flonum-bits)
  (for/fold ([i 0]) ([_  (in-range 4)])
    (+ (* i #x10000) (random #x10000))))

;; Returns a rational flonum with bits uniformly distributed
(define (random-flonum)
  (let loop ()
    (define x (bit-field->flonum (random-flonum-bits)))
    ;; appx. 0.0005 probability this loops
    (if (rational? x) x (loop))))

;; Returns a single flonum with bits uniformly distributed; sometimes -inf.f or +inf.f
(define (random-single-flonum*)
  (define s (random #b10))  ; sign bit
  (define e (random #b100000000))  ; exponent
  (define f (random #b100000000000000000000000))  ; fractional part
  ;; Formula from Wikipedia page on IEEE 754 binary32 format
  (real->single-flonum
   (* (if (= s 0) 1 -1)
      (+ 1 (/ f #b100000000000000000000000))
      (expt 2 (- e 127)))))

;; Returns a rational single flonum with bits uniformly distributed
(define (random-single-flonum)
  (let loop ()
    (define x (random-single-flonum*))
    (if (rational? x) x (loop))))

;; Converts random integers to random exact rationals
(define (random-integer->random-exact-rational E)
  (cond
    [(= E 0)  0]  ; code below would pick 0/0
    [else
     ;; random fraction
     (define n (exact-ceiling (random-exponential E)))
     (define d (exact-ceiling (random-exponential E)))
     (cond
       [(= d 0)  0]  ; appx. probability 1.11e-16
       [else
        (define x (/ n d))
        (if ((random) . < . 0.5) x (- x))])]))

;; Converts random integers to random double flonums
;; Often returns very large or small numbers (near the limits of floating-point range)
;; Sometimes returns +nan.0, +inf.0 or -inf.0
(define (random-integer->random-flonum E)
  (define r (random))
  (cond
    ;; probability 0.25: random flonum, laplace-distributed with scale E
    [(r . < . 0.25)  (random-laplace (abs E))]
    ;; probability 0.25: random flonum with uniform bits
    [(r . < . 0.50)  (random-flonum)]
    ;; probability 0.35: very small or very large flonum
    [(r . < . 0.85)
     (define r (random))
     (cond [(r . < . 0.5)
            (define x (ordinal->flonum E))
            (cond [(= x 0.0)  (if ((random) . < . 0.5) 0.0 -0.0)]
                  [else  x])]
           [(r . < . 0.75)
            (flstep -inf.0 (abs E))]
           [else
            (flstep +inf.0 (- (abs E)))])]
    ;; probability 0.05 each: +nan.0, +inf.0, -inf.0
    [(r . < . 0.90)  +nan.0]
    [(r . < . 0.95)  +inf.0]
    [else            -inf.0]))

;; Converts random integers to random single flonums
;; Sometimes returns +nan.f, +inf.f or -inf.f
(define (random-integer->random-single-flonum E)
  (define r (random))
  (cond
    ;; probability 0.50: random single flonum, laplace-distributed with scale E
    [(r . < . 0.50)  (real->single-flonum (random-laplace (abs E)))]
    ;; probability 0.35: random single flonum with uniform bits
    [(r . < . 0.85)  (random-single-flonum)]
    ;; probability 0.05 each: +nan.f, +inf.f, -inf.f
    [(r . < . 0.90)  +nan.f]
    [(r . < . 0.95)  +inf.f]
    [else            -inf.f]))

;; Converts random integers to random reals
(define (random-integer->random-real E)
  (define r (random))
  ;; probability 0.25 each
  (cond [(r . < . 0.25)  E]
        [(r . < . 0.50)  (random-integer->random-exact-rational E)]
        [(r . < . 0.75)  (random-integer->random-flonum E)]
        [else            (random-integer->random-single-flonum E)]))
