File: test_int_math.ml

package info (click to toggle)
janest-base 0.17.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,632 kB
  • sloc: ml: 48,653; ansic: 281; javascript: 126; makefile: 14
file content (487 lines) | stat: -rw-r--r-- 16,511 bytes parent folder | download | duplicates (2)
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)
;;