Skip to content

Commit

Permalink
Merge pull request #150 from xavierleroy/revised-hash
Browse files Browse the repository at this point in the history
Use standard hash function for `Z.hash` and add `Z.seeded_hash`
  • Loading branch information
xavierleroy authored Jan 3, 2024
2 parents 524a489 + b9c9676 commit 1898327
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 20 deletions.
5 changes: 0 additions & 5 deletions caml_z.c
Original file line number Diff line number Diff line change
Expand Up @@ -3350,11 +3350,6 @@ static intnat ml_z_custom_hash(value v)
return acc;
}

CAMLprim value ml_z_hash(value v)
{
return Val_long(ml_z_custom_hash(v));
}

/* serialized format:
- 1-byte sign (1 for negative, 0 for positive)
- 4-byte size in bytes
Expand Down
2 changes: 1 addition & 1 deletion tests/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ test:: ofstring.exe

test:: chi2.exe
@echo "Testing random number generation..."
@./chi2.exe
@if ./chi2.exe; then echo "chi2: passed"; else echo "chi2: FAILED"; exit 2; fi

bench:: timings.exe
./timings.exe
Expand Down
33 changes: 25 additions & 8 deletions tests/chi2.ml
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
(* Accumulate [n] samples from function [f] and check the chi-square.
Only the low 8 bits of the result of [f] are sampled. *)
Assumes [f] returns integers in the [0..255] range. *)

let chisquare n f =
let r = 256 in
let freq = Array.make r 0 in
for i = 0 to n - 1 do
let t = Z.to_int (Z.logand (f ()) (Z.of_int 0xFF)) in
freq.(t) <- freq.(t) + 1
let t = f () in freq.(t) <- freq.(t) + 1
done;
let expected = float n /. float r in
let t =
Expand All @@ -22,9 +21,19 @@ let chisquare n f =
*)
chi2 <= degfree +. 4.0 *. sqrt (2.0 *. degfree)

let failed = ref false

let test_base name f =
if not (chisquare 100_000 f) then begin
Printf.printf "%s: suspicious result\n%!" name;
failed := true
end

let test name f =
if not (chisquare 100_000 f)
then Printf.printf "%s: suspicious result\n%!" name
(* Test the low 8 bits of the result of f *)
test_base name (fun () -> Z.to_int (Z.logand (f ()) (Z.of_int 0xFF)))

let p = Z.of_string "35742549198872617291353508656626642567"

let _ =
test "random_bits 15 (bits 0-7)"
Expand All @@ -38,6 +47,14 @@ let _ =
test "random_int 2^30 (bits 21-28)"
(fun () -> Z.(shift_right (random_int (shift_left one 30)) 21));
test "random_int (256 * p) / p"
(let p = Z.of_string "35742549198872617291353508656626642567" in
let bound = Z.shift_left p 8 in
fun () -> Z.(div (random_int bound) p))
(let bound = Z.shift_left p 8 in
fun () -> Z.(div (random_int bound) p));
(* Also test our hash function, why not? *)
test_base "hash (random_int p) (bits 0-7)"
(fun () -> Z.(hash (random_int p)) land 0xFF);
test_base "hash (random_int p) (bits 16-23)"
(fun () -> (Z.(hash (random_int p)) lsr 16) land 0xFF);
exit (if !failed then 2 else 0)



3 changes: 2 additions & 1 deletion z.ml
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,8 @@ external perfect_power: t -> bool = "ml_z_perfect_power"
external perfect_square: t -> bool = "ml_z_perfect_square"
external probab_prime: t -> int -> int = "ml_z_probab_prime"
external nextprime: t -> t = "ml_z_nextprime"
external hash: t -> int = "ml_z_hash" [@@noalloc]
let hash: t -> int = Stdlib.Hashtbl.hash
let seeded_hash: int -> t -> int = Stdlib.Hashtbl.seeded_hash
external to_bits: t -> string = "ml_z_to_bits"
external of_bits: string -> t = "ml_z_of_bits"

Expand Down
28 changes: 23 additions & 5 deletions z.mli
Original file line number Diff line number Diff line change
Expand Up @@ -486,12 +486,24 @@ val is_odd: t -> bool
@since 1.4
*)

external hash: t -> int = "ml_z_hash" [@@noalloc]
val hash: t -> int
(** Hashes a number, producing a small integer.
The result is consistent with equality: if [a] = [b], then [hash a] =
[hash b].
OCaml's generic hash function, [Hashtbl.hash], works correctly with
numbers, but {!Z.hash} is slightly faster.
The result is consistent with equality:
if [a] = [b], then [hash a] = [hash b].
The result is the same as produced by OCaml's generic hash function,
{!Hashtbl.hash}.
Together with type {!Z.t}, the function {!Z.hash} makes it possible
to pass module {!Z} as argument to the functor {!Hashtbl.Make}.
@before 1.14 a different hash algorithm was used.
*)

val seeded_hash: int -> t -> int
(** Like {!Z.hash}, but takes a seed as extra argument for diversification.
The result is the same as produced by OCaml's generic seeded hash function,
{!Hashtbl.seeded_hash}.
Together with type {!Z.t}, the function {!Z.hash} makes it possible
to pass module {!Z} as argument to the functor {!Hashtbl.MakeSeeded}.
@since 1.14
*)

(** {1 Elementary number theory} *)
Expand Down Expand Up @@ -717,6 +729,8 @@ val random_int: ?rng: Random.State.t -> t -> t
Random numbers produced by this function are not cryptographically
strong and must not be used in cryptographic or high-security
contexts. See {!Z.random_int_gen} for an alternative.
@since 1.13
*)

val random_bits: ?rng: Random.State.t -> int -> t
Expand All @@ -731,6 +745,8 @@ val random_bits: ?rng: Random.State.t -> int -> t
Random numbers produced by this function are not cryptographically
strong and must not be used in cryptographic or high-security
contexts. See {!Z.random_bits_gen} for an alternative.
@since 1.13
*)

val random_int_gen: fill: (bytes -> int -> int -> unit) -> t -> t
Expand All @@ -751,6 +767,7 @@ val random_int_gen: fill: (bytes -> int -> int -> unit) -> t -> t
<<
Z.random_int_gen ~fill:Cryptokit.Random.secure_rng#bytes bound
>>
@since 1.13
*)

val random_bits_gen: fill: (bytes -> int -> int -> unit) -> int -> t
Expand All @@ -759,6 +776,7 @@ val random_bits_gen: fill: (bytes -> int -> int -> unit) -> int -> t
This is a more efficient special case of {!Z.random_int_gen} when the
bound is a power of two. The [fill] parameter is as described in
{!Z.random_int_gen}.
@since 1.13
*)

(** {1 Prefix and infix operators} *)
Expand Down

0 comments on commit 1898327

Please sign in to comment.