pax_global_header00006660000000000000000000000064146164733610014525gustar00rootroot0000000000000052 comment=0f4a6ef59a640f8ac38a8140f0800895189230fe splittable_random-0.17.0/000077500000000000000000000000001461647336100153155ustar00rootroot00000000000000splittable_random-0.17.0/.gitignore000066400000000000000000000000411461647336100173000ustar00rootroot00000000000000_build *.install *.merlin _opam splittable_random-0.17.0/.ocamlformat000066400000000000000000000000231461647336100176150ustar00rootroot00000000000000profile=janestreet splittable_random-0.17.0/CHANGES.md000066400000000000000000000004541461647336100167120ustar00rootroot00000000000000## Release v0.17.0 * Deprecate `Splittable_random.State`. All definitions previously contained within this module are now included directly in `Splittable_random` itself. ## git version - Fix `mix_odd_gamma` ( #1 ), reported by @Lysxia See http://www.pcg-random.org/posts/bugs-in-splitmix.html splittable_random-0.17.0/CONTRIBUTING.md000066400000000000000000000044101461647336100175450ustar00rootroot00000000000000This repository contains open source software that is developed and maintained by [Jane Street][js]. Contributions to this project are welcome and should be submitted via GitHub pull requests. Signing contributions --------------------- We require that you sign your contributions. Your signature certifies that you wrote the patch or otherwise have the right to pass it on as an open-source patch. The rules are pretty simple: if you can certify the below (from [developercertificate.org][dco]): ``` Developer Certificate of Origin Version 1.1 Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 1 Letterman Drive Suite D4700 San Francisco, CA, 94129 Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. ``` Then you just add a line to every git commit message: ``` Signed-off-by: Joe Smith ``` Use your real name (sorry, no pseudonyms or anonymous contributions.) If you set your `user.name` and `user.email` git configs, you can sign your commit automatically with git commit -s. [dco]: http://developercertificate.org/ [js]: https://opensource.janestreet.com/ splittable_random-0.17.0/LICENSE.md000066400000000000000000000021461461647336100167240ustar00rootroot00000000000000The MIT License Copyright (c) 2018--2024 Jane Street Group, LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. splittable_random-0.17.0/Makefile000066400000000000000000000004031461647336100167520ustar00rootroot00000000000000INSTALL_ARGS := $(if $(PREFIX),--prefix $(PREFIX),) default: dune build install: dune install $(INSTALL_ARGS) uninstall: dune uninstall $(INSTALL_ARGS) reinstall: uninstall install clean: dune clean .PHONY: default install uninstall reinstall clean splittable_random-0.17.0/README.md000066400000000000000000000006671461647336100166050ustar00rootroot00000000000000Splittable_random ================= PRNG that can be split into independent streams A splittable pseudo-random number generator (SPRNG) functions like a PRNG in that it can be used as a stream of random values; it can also be \"split\" to produce a second, independent stream of random values. This library implements a splittable pseudo-random number generator that sacrifices cryptographic-quality randomness in favor of performance. splittable_random-0.17.0/dune000066400000000000000000000000001461647336100161610ustar00rootroot00000000000000splittable_random-0.17.0/dune-project000066400000000000000000000000211461647336100176300ustar00rootroot00000000000000(lang dune 3.11) splittable_random-0.17.0/splittable_random.opam000066400000000000000000000024231461647336100216770ustar00rootroot00000000000000opam-version: "2.0" version: "v0.17.0" maintainer: "Jane Street developers" authors: ["Jane Street Group, LLC"] homepage: "https://github.com/janestreet/splittable_random" bug-reports: "https://github.com/janestreet/splittable_random/issues" dev-repo: "git+https://github.com/janestreet/splittable_random.git" doc: "https://ocaml.janestreet.com/ocaml-core/latest/doc/splittable_random/index.html" license: "MIT" build: [ ["dune" "build" "-p" name "-j" jobs] ] depends: [ "ocaml" {>= "5.1.0"} "base" {>= "v0.17" & < "v0.18"} "ppx_assert" {>= "v0.17" & < "v0.18"} "ppx_bench" {>= "v0.17" & < "v0.18"} "ppx_inline_test" {>= "v0.17" & < "v0.18"} "ppx_sexp_message" {>= "v0.17" & < "v0.18"} "dune" {>= "3.11.0"} ] available: arch != "arm32" & arch != "x86_32" synopsis: "PRNG that can be split into independent streams" description: " PRNG that can be split into independent streams A splittable pseudo-random number generator (SPRNG) functions like a PRNG in that it can be used as a stream of random values; it can also be \"split\" to produce a second, independent stream of random values. This library implements a splittable pseudo-random number generator that sacrifices cryptographic-quality randomness in favor of performance. " splittable_random-0.17.0/src/000077500000000000000000000000001461647336100161045ustar00rootroot00000000000000splittable_random-0.17.0/src/dune000066400000000000000000000002431461647336100167610ustar00rootroot00000000000000(library (name splittable_random) (public_name splittable_random) (libraries base) (preprocess (pps ppx_assert ppx_bench ppx_inline_test ppx_sexp_message))) splittable_random-0.17.0/src/splittable_random.ml000066400000000000000000000272661461647336100221560ustar00rootroot00000000000000(** This module implements "Fast Splittable Pseudorandom Number Generators" by Steele et. al. (1). The paper's algorithm provides decent randomness for most purposes, but sacrifices cryptographic-quality randomness in favor of performance. The original implementation was tested with DieHarder and BigCrush; see the paper for details. Our implementation is a port from Java to OCaml of the paper's algorithm. Other than the choice of initial seed for [create], our port should be faithful. We have not re-run the DieHarder or BigCrush tests on our implementation. Our port is also not as performant as the original; two factors that hurt us are boxed [int64] values and lack of a POPCNT primitive. (1) http://2014.splashcon.org/event/oopsla2014-fast-splittable-pseudorandom-number-generators (also mirrored at http://gee.cs.oswego.edu/dl/papers/oopsla14.pdf) Beware when implementing this interface; it is easy to implement a [split] operation whose output is not as "independent" as it seems (2). This bug caused problems for Haskell's Quickcheck library for a long time. (2) Schaathun, "Evaluation of splittable pseudo-random generators", JFP 2015. http://www.hg.schaathun.net/research/Papers/hgs2015jfp.pdf *) open! Base open Int64.O let is_odd x = x lor 1L = x let popcount = Int64.popcount type t = { mutable seed : int64 ; odd_gamma : int64 } (* Alias used below when [t] is shadowed. *) type state = t let golden_gamma = 0x9e37_79b9_7f4a_7c15L let of_int seed = { seed = Int64.of_int seed; odd_gamma = golden_gamma } let copy { seed; odd_gamma } = { seed; odd_gamma } let mix_bits z n = z lxor (z lsr n) let mix64 z = let z = mix_bits z 33 * 0xff51_afd7_ed55_8ccdL in let z = mix_bits z 33 * 0xc4ce_b9fe_1a85_ec53L in mix_bits z 33 ;; let mix64_variant13 z = let z = mix_bits z 30 * 0xbf58_476d_1ce4_e5b9L in let z = mix_bits z 27 * 0x94d0_49bb_1331_11ebL in mix_bits z 31 ;; let mix_odd_gamma z = let z = mix64_variant13 z lor 1L in let n = popcount (z lxor (z lsr 1)) in (* The original paper uses [>=] in the conditional immediately below; however this is a typo, and we correct it by using [<]. This was fixed in response to [1] and [2]. [1] https://github.com/janestreet/splittable_random/issues/1 [2] http://www.pcg-random.org/posts/bugs-in-splitmix.html *) if Int.( < ) n 24 then z lxor 0xaaaa_aaaa_aaaa_aaaaL else z ;; let%test_unit "odd gamma" = for input = -1_000_000 to 1_000_000 do let output = mix_odd_gamma (Int64.of_int input) in if not (is_odd output) then Error.raise_s [%message "gamma value is not odd" (input : int) (output : int64)] done ;; let next_seed t = let next = t.seed + t.odd_gamma in t.seed <- next; next ;; let of_seed_and_gamma ~seed ~gamma = let seed = mix64 seed in let odd_gamma = mix_odd_gamma gamma in { seed; odd_gamma } ;; let random_int64 random_state = Random.State.int64_incl random_state Int64.min_value Int64.max_value ;; let create random_state = let seed = random_int64 random_state in let gamma = random_int64 random_state in of_seed_and_gamma ~seed ~gamma ;; let split t = let seed = next_seed t in let gamma = next_seed t in of_seed_and_gamma ~seed ~gamma ;; let next_int64 t = mix64 (next_seed t) (* [perturb] is not from any external source, but provides a way to mix in external entropy with a pseudo-random state. *) let perturb t salt = let next = t.seed + mix64 (Int64.of_int salt) in t.seed <- next ;; let bool state = is_odd (next_int64 state) (* We abuse terminology and refer to individual values as biased or unbiased. More properly, what is unbiased is the sampler that results if we keep only these "unbiased" values. *) let remainder_is_unbiased ~draw ~remainder ~draw_maximum ~remainder_maximum = let open Int64.O in draw - remainder <= draw_maximum - remainder_maximum ;; let%test_unit "remainder_is_unbiased" = (* choosing a range of 10 values based on a range of 105 values *) let draw_maximum = 104L in let remainder_maximum = 9L in let is_unbiased draw = let remainder = Int64.rem draw (Int64.succ remainder_maximum) in remainder_is_unbiased ~draw ~remainder ~draw_maximum ~remainder_maximum in for i = 0 to 99 do [%test_result: bool] (is_unbiased (Int64.of_int i)) ~expect:true ~message:(Int.to_string i) done; for i = 100 to 104 do [%test_result: bool] (is_unbiased (Int64.of_int i)) ~expect:false ~message:(Int.to_string i) done ;; (* This implementation of bounded randomness is adapted from [Random.State.int*] in the OCaml standard library. The purpose is to use the minimum number of calls to [next_int64] to produce a number uniformly chosen within the given range. *) let int64 = let open Int64.O in let rec between state ~lo ~hi = let draw = next_int64 state in if lo <= draw && draw <= hi then draw else between state ~lo ~hi in let rec non_negative_up_to state maximum = let draw = next_int64 state land Int64.max_value in let remainder = Int64.rem draw (Int64.succ maximum) in if remainder_is_unbiased ~draw ~remainder ~draw_maximum:Int64.max_value ~remainder_maximum:maximum then remainder else non_negative_up_to state maximum in fun state ~lo ~hi -> if lo > hi then Error.raise_s [%message "int64: crossed bounds" (lo : int64) (hi : int64)]; let diff = hi - lo in if diff = Int64.max_value then (next_int64 state land Int64.max_value) + lo else if diff >= 0L then non_negative_up_to state diff + lo else between state ~lo ~hi ;; let int state ~lo ~hi = let lo = Int64.of_int lo in let hi = Int64.of_int hi in (* truncate unneeded bits *) Int64.to_int_trunc (int64 state ~lo ~hi) ;; let int32 state ~lo ~hi = let lo = Int64.of_int32 lo in let hi = Int64.of_int32 hi in (* truncate unneeded bits *) Int64.to_int32_trunc (int64 state ~lo ~hi) ;; let nativeint state ~lo ~hi = let lo = Int64.of_nativeint lo in let hi = Int64.of_nativeint hi in (* truncate unneeded bits *) Int64.to_nativeint_trunc (int64 state ~lo ~hi) ;; let int63 state ~lo ~hi = let lo = Int63.to_int64 lo in let hi = Int63.to_int64 hi in (* truncate unneeded bits *) Int63.of_int64_trunc (int64 state ~lo ~hi) ;; let double_ulp = 2. **. -53. let%test_unit "double_ulp" = let open Float.O in match Word_size.word_size with | W64 -> assert (1.0 -. double_ulp < 1.0); assert (1.0 -. (double_ulp /. 2.0) = 1.0) | W32 -> (* 32-bit OCaml uses a 64-bit float representation but 80-bit float instructions, so rounding works differently due to the conversion back and forth. *) assert (1.0 -. double_ulp < 1.0); assert (1.0 -. (double_ulp /. 2.0) <= 1.0) ;; let unit_float_from_int64 int64 = Int64.to_float (int64 lsr 11) *. double_ulp let%test_unit "unit_float_from_int64" = let open Float.O in assert (unit_float_from_int64 0x0000_0000_0000_0000L = 0.); assert (unit_float_from_int64 0xffff_ffff_ffff_ffffL < 1.0); assert (unit_float_from_int64 0xffff_ffff_ffff_ffffL = 1.0 -. double_ulp) ;; let unit_float state = unit_float_from_int64 (next_int64 state) (* Note about roundoff error: Although [float state ~lo ~hi] is nominally inclusive of endpoints, we are relying on the fact that [unit_float] never returns 1., because there are pairs [(lo,hi)] for which [lo +. 1. *. (hi -. lo) > hi]. There are also pairs [(lo,hi)] and values of [x] with [x < 1.] such that [lo +. x *. (hi -. lo) = hi], so it would not be correct to document this as being exclusive of [hi]. *) let float = let rec finite_float state ~lo ~hi = let range = hi -. lo in if Float.is_finite range then lo +. (unit_float state *. range) else ( (* If [hi - lo] is infinite, then [hi + lo] is finite because [hi] and [lo] have opposite signs. *) let mid = (hi +. lo) /. 2. in if bool state (* Depending on rounding, the recursion with [~hi:mid] might be inclusive of [mid], which would mean the two cases overlap on [mid]. The alternative is to increment or decrement [mid] using [one_ulp] in either of the calls, but then if the first case is exclusive we leave a "gap" between the two ranges. There's no perfectly uniform solution, so we use the simpler code that does not call [one_ulp]. *) then finite_float state ~lo ~hi:mid else finite_float state ~lo:mid ~hi) in fun state ~lo ~hi -> if not (Float.is_finite lo && Float.is_finite hi) then raise_s [%message "float: bounds are not finite numbers" (lo : float) (hi : float)]; if Float.( > ) lo hi then raise_s [%message "float: bounds are crossed" (lo : float) (hi : float)]; finite_float state ~lo ~hi ;; let%bench_fun "unit_float_from_int64" = let int64 = 1L in fun () -> unit_float_from_int64 int64 ;; module Log_uniform = struct module Make (M : sig include Int.S val uniform : state -> lo:t -> hi:t -> t end) : sig val log_uniform : state -> lo:M.t -> hi:M.t -> M.t end = struct open M let bits_to_represent t = assert (t >= zero); let t = ref t in let n = ref 0 in while !t > zero do t := shift_right !t 1; Int.incr n done; !n ;; let%test_unit "bits_to_represent" = let test n expect = [%test_result: int] (bits_to_represent n) ~expect in test (M.of_int_exn 0) 0; test (M.of_int_exn 1) 1; test (M.of_int_exn 2) 2; test (M.of_int_exn 3) 2; test (M.of_int_exn 4) 3; test (M.of_int_exn 5) 3; test (M.of_int_exn 6) 3; test (M.of_int_exn 7) 3; test (M.of_int_exn 8) 4; test (M.of_int_exn 100) 7; test M.max_value (Int.pred M.num_bits) ;; let min_represented_by_n_bits n = if Int.equal n 0 then zero else shift_left one (Int.pred n) ;; let%test_unit "min_represented_by_n_bits" = let test n expect = [%test_result: M.t] (min_represented_by_n_bits n) ~expect in test 0 (M.of_int_exn 0); test 1 (M.of_int_exn 1); test 2 (M.of_int_exn 2); test 3 (M.of_int_exn 4); test 4 (M.of_int_exn 8); test 7 (M.of_int_exn 64); test (Int.pred M.num_bits) (M.shift_right_logical M.min_value 1) ;; let max_represented_by_n_bits n = pred (shift_left one n) let%test_unit "max_represented_by_n_bits" = let test n expect = [%test_result: M.t] (max_represented_by_n_bits n) ~expect in test 0 (M.of_int_exn 0); test 1 (M.of_int_exn 1); test 2 (M.of_int_exn 3); test 3 (M.of_int_exn 7); test 4 (M.of_int_exn 15); test 7 (M.of_int_exn 127); test (Int.pred M.num_bits) M.max_value ;; let log_uniform state ~lo ~hi = let min_bits = bits_to_represent lo in let max_bits = bits_to_represent hi in let bits = int state ~lo:min_bits ~hi:max_bits in uniform state ~lo:(min_represented_by_n_bits bits |> max lo) ~hi:(max_represented_by_n_bits bits |> min hi) ;; end module For_int = Make (struct include Int let uniform = int end) module For_int32 = Make (struct include Int32 let uniform = int32 end) module For_int63 = Make (struct include Int63 let uniform = int63 end) module For_int64 = Make (struct include Int64 let uniform = int64 end) module For_nativeint = Make (struct include Nativeint let uniform = nativeint end) let int = For_int.log_uniform let int32 = For_int32.log_uniform let int63 = For_int63.log_uniform let int64 = For_int64.log_uniform let nativeint = For_nativeint.log_uniform end module State = struct type t = state let create = create let of_int = of_int let perturb = perturb let copy = copy let split = split end splittable_random-0.17.0/src/splittable_random.mli000066400000000000000000000075761461647336100223310ustar00rootroot00000000000000(** A splittable pseudo-random number generator (SPRNG) functions like a PRNG in that it can be used as a stream of random values; it can also be "split" to produce a second, independent stream of random values. This module implements a splittable pseudo-random number generator that sacrifices cryptographic-quality randomness in favor of performance. The primary difference between [Splittable_random] and {!Random} is the [split] operation for generating new pseudo-random states. While it is easy to simulate [split] using [Random], the result has undesirable statistical properties; the new state does not behave independently of the original. It is better to switch to [Splittable_random] if you need an operation like [split], as this module has been implemented with the statistical properties of splitting in mind. For most other purposes, [Random] is likely a better choice, as its implementation passes all Diehard tests, while [Splittable_random] fails some Diehard tests. *) open! Base type t (** Create a new [t] seeded from the given random state. This allows nondeterministic initialization, for example in the case that the input state was created using [Random.make_self_init]. Constructors like [create] and [of_int] should be called once at the start of a randomized computation and the resulting state should be threaded through. Repeatedly creating splittable random states from seeds in the middle of computation can defeat the SPRNG's splittable properties. *) val create : Random.State.t -> t (** Create a new [t] that will return identical results to any other [t] created with that integer. *) val of_int : int -> t (** [perturb t salt] adds the entropy of [salt] to [t]. *) val perturb : t -> int -> unit (** Create a copy of [t] that will return the same random samples as [t]. *) val copy : t -> t (** [split t] produces a new state that behaves deterministically (i.e. only depending on the state of [t]), but pseudo-independently from [t]. This operation mutates [t], i.e., [t] will return different values than if this hadn't been called. *) val split : t -> t (** Legacy aliases for the preceding definitions. *) module State : sig type nonrec t = t val create : Random.State.t -> t val of_int : int -> t val perturb : t -> int -> unit val copy : t -> t val split : t -> t end [@@deprecated "[since 2023-10] There is no longer any need to use [Splittable_random.State]. Its \ definitions are now included directly in [Splittable_random]."] (** Produces a random, fair boolean. *) val bool : t -> bool (** Produce a random number uniformly distributed in the given inclusive range. (In the case of [float], [hi] may or may not be attainable, depending on rounding.) *) val int : t -> lo:int -> hi:int -> int val int32 : t -> lo:int32 -> hi:int32 -> int32 val int63 : t -> lo:Int63.t -> hi:Int63.t -> Int63.t val int64 : t -> lo:int64 -> hi:int64 -> int64 val nativeint : t -> lo:nativeint -> hi:nativeint -> nativeint val float : t -> lo:float -> hi:float -> float (** [unit_float state = float state ~lo:0. ~hi:1.], but slightly more efficient (and right endpoint is exclusive). *) val unit_float : t -> float module Log_uniform : sig (** Produce a random number in the given inclusive range, where the number of bits in the representation is chosen uniformly based on the given range, and then the value is chosen uniformly within the range restricted to the chosen bit width. Raises if [lo < 0 || hi < lo]. These functions are useful for choosing numbers that are weighted low within a given range. *) val int : t -> lo:int -> hi:int -> int val int32 : t -> lo:int32 -> hi:int32 -> int32 val int63 : t -> lo:Int63.t -> hi:Int63.t -> Int63.t val int64 : t -> lo:int64 -> hi:int64 -> int64 val nativeint : t -> lo:nativeint -> hi:nativeint -> nativeint end splittable_random-0.17.0/test/000077500000000000000000000000001461647336100162745ustar00rootroot00000000000000splittable_random-0.17.0/test/dune000066400000000000000000000002051461647336100171470ustar00rootroot00000000000000(library (name splittable_random_test) (libraries base expect_test_helpers_core splittable_random) (preprocess (pps ppx_jane))) splittable_random-0.17.0/test/splittable_random_test.ml000066400000000000000000000163611461647336100233770ustar00rootroot00000000000000open! Core open Expect_test_helpers_core let%expect_test "bool fairness" = let open Int.O in let trial_count = 1_000 in let failures = ref [] in for seed = 1 to trial_count do let state = Splittable_random.of_int seed in let true_count = ref 0 in let false_count = ref 0 in for _ = 1 to 1_000 do if Splittable_random.bool state then Int.incr true_count else Int.incr false_count done; let diff = Int.abs (!true_count - !false_count) in (* Note that diff > 100 is a 3.2-sigma event, and should occur with probability roughly 1 in 1000. *) if diff > 100 then ( let sexp = [%message (seed : int) (true_count : int ref) (false_count : int ref)] in failures := sexp :: !failures) done; let failure_count = List.length !failures in let failure_rate = Percent.of_mult (Float.of_int failure_count /. Float.of_int trial_count) in print_s [%message (failure_rate : Percent.t) (failures : Sexp.t list ref)]; require [%here] (Percent.( < ) failure_rate (Percent.of_percentage 1.)); [%expect {| ((failure_rate 10bp) (failures (( (seed 573) (true_count 551) (false_count 449))))) |}] ;; let%test_module "int64" = (module struct open Int64.O let bounds = [ Int64.min_value ; Int64.min_value + 1L ; Int64.min_value + 2L ; -1_000_000_000L ; -1_000_000L ; -1_000L ; -100L ; -10L ; -2L ; -1L ; 0L ; 1L ; 2L ; 10L ; 100L ; 1_000L ; 1_000_000L ; 1_000_000_000L ; Int64.max_value - 2L ; Int64.max_value - 1L ; Int64.max_value ] ;; let%test_unit "bounds" = let open Int64.O in let state = Splittable_random.of_int 0 in List.iter bounds ~f:(fun lo -> List.iter bounds ~f:(fun hi -> if lo <= hi then for _ = 1 to 1_000 do let choice = Splittable_random.int64 state ~lo ~hi in if choice < lo || choice > hi then Error.raise_s [%message "out of bounds" (choice : int64) (lo : int64) (hi : int64)] done)) ;; let%test_unit "coverage" = let state = Splittable_random.of_int 0 in List.iter [ 1L; 10L; 100L; 1000L ] ~f:(fun range -> let lo = 0L in let hi = Int64.pred range in for _ = 1 to 100 do let count = Array.init (Int64.to_int_exn range) ~f:(fun _ -> 0) in for _ = 1 to Int64.to_int_exn (range * 100L) do let i = Splittable_random.int64 state ~lo ~hi |> Int64.to_int_exn in count.(i) <- Int.succ count.(i) done; Array.iteri count ~f:(fun value count -> if Int.equal count 0 then Error.raise_s [%message "failed to generate value" (value : int) (lo : int64) (hi : int64)]) done) ;; (* This should return values with mean 0 and variance 1 if implementation is correct. *) let test_bias_of_mean ~lo ~hi ~sample_size state = let open Int64.O in assert (lo < hi); let lof = Int64.to_float lo in let hif = Int64.to_float hi in let delta = hif -. lof in let draw () = (Int64.to_float (Splittable_random.int64 state ~lo ~hi) -. lof) /. delta in let rec loop iters accum = if iters <= 0L then accum else loop (iters - 1L) (accum +. draw ()) in let sum = loop sample_size 0. in let mean = sum /. Int64.to_float sample_size in let n = delta +. 1. in (* We have n evenly spaced values from 0 to 1 inclusive, each with probability 1/n. This has variance (n+1)/(12(n-1)) per draw. *) let standard_error = Float.sqrt ((n +. 1.) /. (12. *. (n -. 1.) *. Int64.to_float sample_size)) in (mean -. 0.5) /. standard_error ;; let%test_unit "bias" = let open Float.O in let hi = 3689348814741910528L in (* about 0.4 * max_int *) let z = test_bias_of_mean ~lo:0L ~hi ~sample_size:1000L (Splittable_random.of_int 0) in assert (Stdlib.abs_float z < 3.) ;; end) ;; let%test_module "float" = (module struct let bounds = [ Float.neg Float.max_finite_value ; -1_000_000_000. ; -1. ; -0.000_000_001 ; 0. ; 0.000_000_001 ; 1. ; 1_000_000_000. ; Float.max_finite_value ] ;; let%test_unit "bounds" = let open Float.O in let state = Splittable_random.of_int 0 in List.iter bounds ~f:(fun lo -> List.iter bounds ~f:(fun hi -> if lo < hi then for _ = 1 to 1000 do let float = Splittable_random.float state ~lo ~hi in if float < lo || float > hi then Error.raise_s [%message "float out of bounds" (float : float) (lo : float) (hi : float)] done)) ;; let%test_unit "coverage" = let open Float.O in let state = Splittable_random.of_int 0 in List.iter bounds ~f:(fun lo -> List.iter bounds ~f:(fun hi -> if lo < hi then for _ = 1 to 100 do let hi' = (lo *. 0.01) +. (hi *. 0.99) in let lo' = (lo *. 0.99) +. (hi *. 0.01) in let mid1 = (lo *. 0.51) +. (hi *. 0.49) in let mid2 = (lo *. 0.49) +. (hi *. 0.51) in let saw_hi = ref false in let saw_lo = ref false in let saw_mid = ref false in for _ = 1 to 1000 do let float = Splittable_random.float state ~lo ~hi in if float < lo' then saw_lo := true; if float > hi' then saw_hi := true; if float > mid1 && float < mid2 then saw_mid := true done; if not (!saw_lo && !saw_mid && !saw_hi) then Error.raise_s [%message "did not get coverage of lo, mid, and hi values" (lo : float) (hi : float) (!saw_lo : bool) (!saw_hi : bool) (!saw_mid : bool)] done)) ;; let%expect_test "error cases" = let state = Splittable_random.of_int 0 in let test lo hi = require_does_raise [%here] (fun () -> Splittable_random.float state ~lo ~hi) in (* NaN bounds *) test Float.nan 0.; [%expect {| ("float: bounds are not finite numbers" (lo NAN) (hi 0)) |}]; test 0. Float.nan; [%expect {| ("float: bounds are not finite numbers" (lo 0) (hi NAN)) |}]; (* infinite bounds *) test Float.neg_infinity 0.; [%expect {| ("float: bounds are not finite numbers" (lo -INF) (hi 0)) |}]; test 0. Float.infinity; [%expect {| ("float: bounds are not finite numbers" (lo 0) (hi INF)) |}]; (* crossed bounds *) test 2. 1.; [%expect {| ("float: bounds are crossed" (lo 2) (hi 1)) |}] ;; end) ;; splittable_random-0.17.0/test/splittable_random_test.mli000066400000000000000000000000551461647336100235410ustar00rootroot00000000000000(*_ This signature is deliberately empty. *)