mte/unikernel/duniverse/base/test/test_int_math.ml
2025-11-11 02:07:51 +01:00

487 lines
16 KiB
OCaml

open! Import
open! Base.Int_math
open! Base.Int_math.Private
let%test_unit _ =
let x =
match Word_size.word_size with
| W32 -> 9
| W64 -> 10
in
for i = 0 to x do
for j = 0 to x do
assert (int_pow i j = Stdlib.(int_of_float (float_of_int i ** float_of_int j)))
done
done
;;
module Test (X : Make_arg) : sig end = struct
open X
include Make (X)
let%test_module "integer-rounding" =
(module struct
let check dir ~range:(lower, upper) ~modulus expected =
let modulus = of_int_exn modulus in
let expected = of_int_exn expected in
for i = lower to upper do
let observed = round ~dir ~to_multiple_of:modulus (of_int_exn i) in
if observed <> expected then raise_s [%message "invalid result" (i : int)]
done
;;
let%test_unit _ = check ~modulus:10 `Down ~range:(10, 19) 10
let%test_unit _ = check ~modulus:10 `Down ~range:(0, 9) 0
let%test_unit _ = check ~modulus:10 `Down ~range:(-10, -1) (-10)
let%test_unit _ = check ~modulus:10 `Down ~range:(-20, -11) (-20)
let%test_unit _ = check ~modulus:10 `Up ~range:(11, 20) 20
let%test_unit _ = check ~modulus:10 `Up ~range:(1, 10) 10
let%test_unit _ = check ~modulus:10 `Up ~range:(-9, 0) 0
let%test_unit _ = check ~modulus:10 `Up ~range:(-19, -10) (-10)
let%test_unit _ = check ~modulus:10 `Zero ~range:(10, 19) 10
let%test_unit _ = check ~modulus:10 `Zero ~range:(-9, 9) 0
let%test_unit _ = check ~modulus:10 `Zero ~range:(-19, -10) (-10)
let%test_unit _ = check ~modulus:10 `Nearest ~range:(15, 24) 20
let%test_unit _ = check ~modulus:10 `Nearest ~range:(5, 14) 10
let%test_unit _ = check ~modulus:10 `Nearest ~range:(-5, 4) 0
let%test_unit _ = check ~modulus:10 `Nearest ~range:(-15, -6) (-10)
let%test_unit _ = check ~modulus:10 `Nearest ~range:(-25, -16) (-20)
let%test_unit _ = check ~modulus:5 `Nearest ~range:(8, 12) 10
let%test_unit _ = check ~modulus:5 `Nearest ~range:(3, 7) 5
let%test_unit _ = check ~modulus:5 `Nearest ~range:(-2, 2) 0
let%test_unit _ = check ~modulus:5 `Nearest ~range:(-7, -3) (-5)
let%test_unit _ = check ~modulus:5 `Nearest ~range:(-12, -8) (-10)
end)
;;
let%test_module "remainder-and-modulus" =
(module struct
let one = of_int_exn 1
let check_integers x y =
let sexp_of_t t = sexp_of_string (to_string t) in
let check_raises f what =
match f () with
| exception _ -> ()
| z ->
raise_s
[%message
"produced result instead of raising"
(what : string)
(x : t)
(y : t)
(z : t)]
in
let check_true cond what =
if not cond then raise_s [%message "failed" (what : string) (x : t) (y : t)]
in
if y = zero
then (
check_raises (fun () -> x / y) "division by zero";
check_raises (fun () -> rem x y) "rem _ zero";
check_raises (fun () -> x % y) "_ % zero";
check_raises (fun () -> x /% y) "_ /% zero")
else (
if x < zero
then check_true (rem x y <= zero) "non-positive remainder"
else check_true (rem x y >= zero) "non-negative remainder";
check_true (abs (rem x y) <= abs y - one) "range of remainder";
if y < zero
then (
check_raises (fun () -> x % y) "_ % negative";
check_raises (fun () -> x /% y) "_ /% negative")
else (
check_true (x = (x /% y * y) + (x % y)) "(/%) and (%) identity";
check_true (x = (x / y * y) + rem x y) "(/) and rem identity";
check_true (x % y >= zero) "non-negative (%)";
check_true (x % y <= y - one) "range of (%)";
if x > zero && y > zero
then (
check_true (x /% y = x / y) "(/%) and (/) identity";
check_true (x % y = rem x y) "(%) and rem identity")))
;;
let check_natural_numbers x y =
List.iter
[ x; -x; x + one; -(x + one) ]
~f:(fun x ->
List.iter [ y; -y; y + one; -(y + one) ] ~f:(fun y -> check_integers x y))
;;
let%test_unit "deterministic" =
let big1 = of_int_exn 118_310_344 in
let big2 = of_int_exn 828_172_408 in
(* Important to test the case where one value is a multiple of the other. Note that
the [x + one] and [y + one] cases in [check_natural_numbers] ensure that we also
test non-multiple cases. *)
assert (big2 = big1 * of_int_exn 7);
let values = [ zero; one; big1; big2 ] in
List.iter values ~f:(fun x ->
List.iter values ~f:(fun y -> check_natural_numbers x y))
;;
let%test_unit "random" =
let rand = Random.State.make [| 8; 67; -5_309 |] in
for _ = 0 to 1_000 do
let max_value = 1_000_000_000 in
let x = of_int_exn (Random.State.int rand max_value) in
let y = of_int_exn (Random.State.int rand max_value) in
check_natural_numbers x y
done
;;
end)
;;
end
include Test (Int)
include Test (Int32)
include Test (Int63)
include Test (Int64)
include Test (Nativeint)
let%test_module "int rounding quickcheck tests" =
(module struct
module type With_quickcheck = sig
type t [@@deriving sexp_of]
include Make_arg with type t := t
val min_value : t
val max_value : t
val quickcheck_generator_incl : t -> t -> t Base_quickcheck.Generator.t
val quickcheck_generator_log_incl : t -> t -> t Base_quickcheck.Generator.t
end
module Rounding_direction = struct
type t =
[ `Up
| `Down
| `Zero
| `Nearest
]
[@@deriving enumerate, sexp_of]
end
module Rounding_pair (Integer : With_quickcheck) = struct
type t =
{ number : Integer.t
; factor : Integer.t
}
[@@deriving sexp_of]
let quickcheck_generator =
(* This generator should frequently generate "interesting" numbers for rounding. *)
let open Base_quickcheck.Generator.Let_syntax in
(* First we choose a factor to round to. *)
let%bind factor =
Integer.quickcheck_generator_log_incl (Integer.of_int_exn 1) Integer.max_value
in
(* Then we choose a multiplier for that factor. *)
let%map multiplier =
Integer.quickcheck_generator_incl
(Integer.( / ) Integer.min_value factor)
(Integer.( / ) Integer.max_value factor)
(* Then we choose an offset such that [multiplier * factor] is the nearest value
to round to. [quickcheck_generator_incl] puts extra weight on the [-factor/2,
factor/2] bounds, and we also weight 0 heavily. *)
and offset =
let half_factor = Integer.( / ) factor (Integer.of_int_exn 2) in
Base_quickcheck.Generator.weighted_union
[ 9., Integer.quickcheck_generator_incl (Integer.neg half_factor) half_factor
; 1., Base_quickcheck.Generator.return Integer.zero
]
in
let number = Integer.( + ) offset (Integer.( * ) factor multiplier) in
{ number; factor }
;;
let quickcheck_shrinker = Base_quickcheck.Shrinker.atomic
end
let test_direction (module Integer : With_quickcheck) ~dir =
let open Integer in
(* Criterion for correct rounding: must be a multiple of the factor *)
let is_multiple_of number ~factor = factor * (number / factor) = number in
(* Criterion for correct rounding: must not reverse sign *)
let is_compatible_sign number ~rounded =
if number > zero
then rounded >= zero
else if number < zero
then rounded <= zero
else rounded = zero
in
(* Criterion for correct rounding: must be less than factor away from original *)
let is_close_enough x y ~factor =
if x > y
then x - y > zero && x - y < factor
else if x < y
then y - x > zero && y - x < factor
else true
in
(* Criterion for correct rounding: rounding direction must be respected *)
let is_in_correct_direction number ~dir ~rounded ~factor =
match dir with
| `Down -> rounded <= number
| `Up -> rounded >= number
| `Zero ->
if number < zero
then rounded >= number
else if number > zero
then rounded <= number
else rounded = zero
| `Nearest ->
if rounded > number
then rounded - number <= number - (rounded - factor)
else if rounded < number
then number - rounded < rounded + factor - number
else true
in
(* Correct rounding obeys all four criteria *)
let is_rounded_correctly number ~dir ~factor ~rounded =
is_multiple_of rounded ~factor
&& is_compatible_sign number ~rounded
&& is_close_enough number rounded ~factor
&& is_in_correct_direction number ~dir ~rounded ~factor
in
(* Round correctly by finding a multiple of the factor, and trying +/-factor away
from that. If this returns [None], there should be no correct representable
result. *)
let round_correctly number ~dir ~factor =
let rounded0 = factor * (number / factor) in
match
List.filter
[ rounded0 - factor; rounded0; rounded0 + factor ]
~f:(fun rounded -> is_rounded_correctly number ~dir ~factor ~rounded)
with
| [] -> None
| [ rounded ] -> Some rounded
| multiple ->
raise_s
[%sexp
"test bug: multiple correctly rounded values", (multiple : Integer.t list)]
in
let module Math = Make (Integer) in
let module Pair = Rounding_pair (Integer) in
require_does_not_raise [%here] (fun () ->
Base_quickcheck.Test.run_exn
(module Pair)
~f:(fun ({ number; factor } : Pair.t) ->
let rounded = Math.round number ~dir ~to_multiple_of:factor in
(* Test that if it is possible to round correctly, then we do. *)
match round_correctly number ~dir ~factor with
| None ->
if is_rounded_correctly number ~dir ~factor ~rounded
then
raise_s
[%sexp
"test bug: did not find correctly rounded value"
, { rounded : Integer.t }]
| Some rounded_correctly ->
if rounded <> rounded_correctly
then
raise_s
[%sexp
"rounding failed"
, { rounded : Integer.t; rounded_correctly : Integer.t }]))
;;
let test m =
List.iter Rounding_direction.all ~f:(fun dir ->
print_s [%sexp "testing", (dir : Rounding_direction.t)];
test_direction m ~dir)
;;
let%expect_test ("int" [@tags "no-js", "64-bits-only"]) =
test
(module struct
include Int
let quickcheck_generator_incl = Base_quickcheck.Generator.int_inclusive
let quickcheck_generator_log_incl = Base_quickcheck.Generator.int_log_inclusive
end);
[%expect
{|
(testing Up)
(testing Down)
(testing Zero)
(testing Nearest)
|}]
;;
let%expect_test "int32" =
test
(module struct
include Int32
let quickcheck_generator_incl = Base_quickcheck.Generator.int32_inclusive
let quickcheck_generator_log_incl =
Base_quickcheck.Generator.int32_log_inclusive
;;
end);
[%expect
{|
(testing Up)
(testing Down)
(testing Zero)
(testing Nearest)
|}]
;;
let%expect_test "int63" =
test
(module struct
include Int63
let quickcheck_generator_incl = Base_quickcheck.Generator.int63_inclusive
let quickcheck_generator_log_incl =
Base_quickcheck.Generator.int63_log_inclusive
;;
end);
[%expect
{|
(testing Up)
(testing Down)
(testing Zero)
(testing Nearest)
|}]
;;
let%expect_test "int64" =
test
(module struct
include Int64
let quickcheck_generator_incl = Base_quickcheck.Generator.int64_inclusive
let quickcheck_generator_log_incl =
Base_quickcheck.Generator.int64_log_inclusive
;;
end);
[%expect
{|
(testing Up)
(testing Down)
(testing Zero)
(testing Nearest)
|}]
;;
let%expect_test ("nativeint" [@tags "no-js", "64-bits-only"]) =
test
(module struct
include Nativeint
let quickcheck_generator_incl = Base_quickcheck.Generator.nativeint_inclusive
let quickcheck_generator_log_incl =
Base_quickcheck.Generator.nativeint_log_inclusive
;;
end);
[%expect
{|
(testing Up)
(testing Down)
(testing Zero)
(testing Nearest)
|}]
;;
end)
;;
let%test_module "pow" =
(module struct
let%test _ = int_pow 0 0 = 1
let%test _ = int_pow 0 1 = 0
let%test _ = int_pow 10 1 = 10
let%test _ = int_pow 10 2 = 100
let%test _ = int_pow 10 3 = 1_000
let%test _ = int_pow 10 4 = 10_000
let%test _ = int_pow 10 5 = 100_000
let%test _ = int_pow 2 10 = 1024
let%test _ = int_pow 0 1_000_000 = 0
let%test _ = int_pow 1 1_000_000 = 1
let%test _ = int_pow (-1) 1_000_000 = 1
let%test _ = int_pow (-1) 1_000_001 = -1
let ( = ) = Int64.( = )
let%test _ = int64_pow 0L 0L = 1L
let%test _ = int64_pow 0L 1_000_000L = 0L
let%test _ = int64_pow 1L 1_000_000L = 1L
let%test _ = int64_pow (-1L) 1_000_000L = 1L
let%test _ = int64_pow (-1L) 1_000_001L = -1L
let%test _ = int64_pow 10L 1L = 10L
let%test _ = int64_pow 10L 2L = 100L
let%test _ = int64_pow 10L 3L = 1_000L
let%test _ = int64_pow 10L 4L = 10_000L
let%test _ = int64_pow 10L 5L = 100_000L
let%test _ = int64_pow 2L 10L = 1_024L
let%test _ = int64_pow 5L 27L = 7450580596923828125L
let exception_thrown pow b e = Exn.does_raise (fun () -> pow b e)
let%test _ = exception_thrown int_pow 10 60
let%test _ = exception_thrown int64_pow 10L 60L
let%test _ = exception_thrown int_pow 10 (-1)
let%test _ = exception_thrown int64_pow 10L (-1L)
let%test _ = exception_thrown int64_pow 2L 63L
let%test _ = not (exception_thrown int64_pow 2L 62L)
let%test _ = exception_thrown int64_pow (-2L) 63L
let%test _ = not (exception_thrown int64_pow (-2L) 62L)
end)
;;
let%test_module "overflow_bounds" =
(module struct
module Pow_overflow_bounds = Pow_overflow_bounds
let%test _ = Int.equal Pow_overflow_bounds.overflow_bound_max_int_value Int.max_value
let%test _ =
Int64.equal Pow_overflow_bounds.overflow_bound_max_int64_value Int64.max_value
;;
module Big_int = struct
include Big_int
let ( > ) = gt_big_int
let ( = ) = eq_big_int
let ( ^ ) = power_big_int_positive_int
let ( + ) = add_big_int
let one = unit_big_int
let to_string = string_of_big_int
end
let test_overflow_table tbl conv max_val =
assert (Array.length tbl = 64);
let max_val = conv max_val in
Array.iteri tbl ~f:(fun i max_base ->
let max_base = conv max_base in
let overflows b = Big_int.(b ^ i > max_val) in
let is_ok =
if i = 0
then Big_int.(max_base = max_val)
else (not (overflows max_base)) && overflows Big_int.(max_base + one)
in
if not is_ok
then
Printf.failwithf
"overflow table check failed for %s (index %d)"
(Big_int.to_string max_base)
i
())
;;
let%test_unit _ =
test_overflow_table
Pow_overflow_bounds.int_positive_overflow_bounds
Big_int.big_int_of_int
Int.max_value
;;
let%test_unit _ =
test_overflow_table
Pow_overflow_bounds.int64_positive_overflow_bounds
Big_int.big_int_of_int64
Int64.max_value
;;
end)
;;