open! Import open! Random let%test_module "State" = (module struct include State let%test_unit ("random int above 2^30" [@tags "64-bits-only"]) = let state = make [| 1; 2; 3; 4; 5 |] in for _ = 1 to 100 do let bound = Int.shift_left 1 40 in let n = int state bound in if n < 0 || n >= bound then failwith (Printf.sprintf "random result %d out of bounds (0,%d)" n (bound - 1)) done ;; end) ;; external random_seed : unit -> Stdlib.Obj.t = "caml_sys_random_seed" let%test_unit _ = (* test that the return type of "caml_sys_random_seed" is what we expect *) let module Obj = Stdlib.Obj in let obj = random_seed () in assert (Obj.is_block obj); assert (Obj.tag obj = Obj.tag (Obj.repr [| 13 |])); for i = 0 to Obj.size obj - 1 do assert (Obj.is_int (Obj.field obj i)) done ;; module type T = sig type t [@@deriving compare, sexp_of] end (* We test that [count] trials of [generate ()] all produce values between [min, max], and generate at least one value between [lo, hi]. *) let test (type t) here m count generate ~min ~max ~check_range:(lo, hi) = let (module T : T with type t = t) = m in let between t ~lower_bound ~upper_bound = T.compare t lower_bound >= 0 && T.compare t upper_bound <= 0 in let generated = List.init count ~f:(fun _ -> generate ()) |> List.dedup_and_sort ~compare:T.compare in require here (List.for_all generated ~f:(fun t -> between t ~lower_bound:min ~upper_bound:max)) ~if_false_then_print_s: (lazy [%message "generated values outside of bounds" (min : T.t) (max : T.t) (generated : T.t list)]); require here (List.exists generated ~f:(fun t -> between t ~lower_bound:lo ~upper_bound:hi)) ~if_false_then_print_s: (lazy [%message "did not generate value inside range" (lo : T.t) (hi : T.t) (generated : T.t list)]) ;; let%expect_test "float" = test [%here] (module Float) 1_000 (fun () -> float 100.) ~min:0. ~max:100. ~check_range:(10., 20.); [%expect {| |}] ;; let%expect_test "float_range" = test [%here] (module Float) 1_000 (fun () -> float_range (-100.) 100.) ~min:(-100.) ~max:100. ~check_range:(-20., -10.); [%expect {| |}] ;; let%expect_test "int" = test [%here] (module Int) 1_000 (fun () -> int 100) ~min:0 ~max:99 ~check_range:(10, 20); [%expect {| |}] ;; let%expect_test "int_incl" = test [%here] (module Int) 1_000 (fun () -> int_incl (-100) 100) ~min:(-100) ~max:100 ~check_range:(-20, -10); [%expect {| |}]; test [%here] (module Int) 1_000 (fun () -> int_incl 0 Int.max_value) ~min:0 ~max:Int.max_value ~check_range:(0, Int.max_value / 100); [%expect {| |}]; test [%here] (module Int) 1_000 (fun () -> int_incl Int.min_value Int.max_value) ~min:Int.min_value ~max:Int.max_value ~check_range:(Int.min_value / 100, Int.max_value / 100); [%expect {| |}] ;; let%expect_test "int32" = test [%here] (module Int32) 1_000 (fun () -> int32 100l) ~min:0l ~max:99l ~check_range:(10l, 20l); [%expect {| |}] ;; let%expect_test "int32_incl" = test [%here] (module Int32) 1_000 (fun () -> int32_incl (-100l) 100l) ~min:(-100l) ~max:100l ~check_range:(-20l, -10l); [%expect {| |}]; test [%here] (module Int32) 1_000 (fun () -> int32_incl 0l Int32.max_value) ~min:0l ~max:Int32.max_value ~check_range:(0l, Int32.( / ) Int32.max_value 100l); [%expect {| |}]; test [%here] (module Int32) 1_000 (fun () -> int32_incl Int32.min_value Int32.max_value) ~min:Int32.min_value ~max:Int32.max_value ~check_range:(Int32.( / ) Int32.min_value 100l, Int32.( / ) Int32.max_value 100l); [%expect {| |}] ;; let%expect_test "int64" = test [%here] (module Int64) 1_000 (fun () -> int64 100L) ~min:0L ~max:99L ~check_range:(10L, 20L); [%expect {| |}] ;; let%expect_test "int64_incl" = test [%here] (module Int64) 1_000 (fun () -> int64_incl (-100L) 100L) ~min:(-100L) ~max:100L ~check_range:(-20L, -10L); [%expect {| |}]; test [%here] (module Int64) 1_000 (fun () -> int64_incl 0L Int64.max_value) ~min:0L ~max:Int64.max_value ~check_range:(0L, Int64.( / ) Int64.max_value 100L); [%expect {| |}]; test [%here] (module Int64) 1_000 (fun () -> int64_incl Int64.min_value Int64.max_value) ~min:Int64.min_value ~max:Int64.max_value ~check_range:(Int64.( / ) Int64.min_value 100L, Int64.( / ) Int64.max_value 100L); [%expect {| |}] ;; let%expect_test "nativeint" = test [%here] (module Nativeint) 1_000 (fun () -> nativeint 100n) ~min:0n ~max:99n ~check_range:(10n, 20n); [%expect {| |}] ;; let%expect_test "nativeint_incl" = test [%here] (module Nativeint) 1_000 (fun () -> nativeint_incl (-100n) 100n) ~min:(-100n) ~max:100n ~check_range:(-20n, -10n); [%expect {| |}]; test [%here] (module Nativeint) 1_000 (fun () -> nativeint_incl 0n Nativeint.max_value) ~min:0n ~max:Nativeint.max_value ~check_range:(0n, Nativeint.( / ) Nativeint.max_value 100n); [%expect {| |}]; test [%here] (module Nativeint) 1_000 (fun () -> nativeint_incl Nativeint.min_value Nativeint.max_value) ~min:Nativeint.min_value ~max:Nativeint.max_value ~check_range: (Nativeint.( / ) Nativeint.min_value 100n, Nativeint.( / ) Nativeint.max_value 100n); [%expect {| |}] ;; (* The int63 functions come from [Int63] rather than [Random], but we test them here along with the others anyway. *) let%expect_test "int63" = let i = Int63.of_int in test [%here] (module Int63) 1_000 (fun () -> Int63.random (i 100)) ~min:(i 0) ~max:(i 99) ~check_range:(i 10, i 20); [%expect {| |}] ;; let%expect_test "int63_incl" = let i = Int63.of_int in test [%here] (module Int63) 1_000 (fun () -> Int63.random_incl (i (-100)) (i 100)) ~min:(i (-100)) ~max:(i 100) ~check_range:(i (-20), i (-10)); [%expect {| |}]; test [%here] (module Int63) 1_000 (fun () -> Int63.random_incl (i 0) Int63.max_value) ~min:(i 0) ~max:Int63.max_value ~check_range:(i 0, Int63.( / ) Int63.max_value (i 100)); [%expect {| |}]; test [%here] (module Int63) 1_000 (fun () -> Int63.random_incl Int63.min_value Int63.max_value) ~min:Int63.min_value ~max:Int63.max_value ~check_range:(Int63.( / ) Int63.min_value (i 100), Int63.( / ) Int63.max_value (i 100)); [%expect {| |}] ;; let%expect_test "ascii" = test [%here] (module Char) 1_000 ascii ~min:Char.min_value ~max:(Char.of_int_exn 127) ~check_range:('a', 'z'); [%expect {| |}] ;; let%expect_test "char" = test [%here] (module Char) 1_000 char ~min:Char.min_value ~max:Char.max_value ~check_range:('\128', '\255'); [%expect {| |}] ;; let%test_module "float upper bound is inclusive despite docs" = (module struct (* The fact that this test passes doesn't demonstrate that the bug has gone away, since the test was explicitly contrived to provoke the bug. *) (* This bug is more clearly illustrated by copying the implementation of [Random.float] from the stdlib (which is just re-exported by Base). Basically, when [r1 /. scale +. r2] requires more than 53 bits of precision, and [bits2] consists of all 1s, rounding causes [rawfloat] to return 1. *) let rawfloat bits1 bits2 = let scale = 1073741824.0 and r1 = Stdlib.float bits1 and r2 = Stdlib.float bits2 in ((r1 /. scale) +. r2) /. scale ;; let%expect_test "likelihood of failure" = (* test 256 states of the random number generator, highest as 60-bit numbers, out of which 64 would have yield a float exactly equal to 1 if [Random.State.float] was not recursive. *) let lbound = (1 lsl 30) - (1 lsl 8) in let ubound = (1 lsl 30) - 1 in let bits2 = ubound in let failures = ref 0 in for bits1 = lbound to ubound do let open Float.O in if rawfloat bits1 bits2 >= 1. then Int.incr failures done; let prob = Stdlib.float !failures *. 0x1p-60 in print_s [%message "likelihood of failure" (failures : int ref) (prob : float)]; [%expect {| ("likelihood of failure" (failures 64) (prob 5.5511151231257827E-17)) |}] ;; end) ;;