1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487
|
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)
;;
|