-
-
Notifications
You must be signed in to change notification settings - Fork 31.5k
/
Copy pathtest_statistics.py
3335 lines (2820 loc) · 131 KB
/
test_statistics.py
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
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
x = """Test suite for statistics module, including helper NumericTestCase and
approx_equal function.
"""
import bisect
import collections
import collections.abc
import copy
import decimal
import doctest
import itertools
import math
import pickle
import random
import sys
import unittest
from test import support
from test.support import import_helper, requires_IEEE_754
from decimal import Decimal
from fractions import Fraction
# Module to be tested.
import statistics
# === Helper functions and class ===
# Test copied from Lib/test/test_math.py
# detect evidence of double-rounding: fsum is not always correctly
# rounded on machines that suffer from double rounding.
x, y = 1e16, 2.9999 # use temporary values to defeat peephole optimizer
HAVE_DOUBLE_ROUNDING = (x + y == 1e16 + 4)
def sign(x):
"""Return -1.0 for negatives, including -0.0, otherwise +1.0."""
return math.copysign(1, x)
def _nan_equal(a, b):
"""Return True if a and b are both the same kind of NAN.
>>> _nan_equal(Decimal('NAN'), Decimal('NAN'))
True
>>> _nan_equal(Decimal('sNAN'), Decimal('sNAN'))
True
>>> _nan_equal(Decimal('NAN'), Decimal('sNAN'))
False
>>> _nan_equal(Decimal(42), Decimal('NAN'))
False
>>> _nan_equal(float('NAN'), float('NAN'))
True
>>> _nan_equal(float('NAN'), 0.5)
False
>>> _nan_equal(float('NAN'), Decimal('NAN'))
False
NAN payloads are not compared.
"""
if type(a) is not type(b):
return False
if isinstance(a, float):
return math.isnan(a) and math.isnan(b)
aexp = a.as_tuple()[2]
bexp = b.as_tuple()[2]
return (aexp == bexp) and (aexp in ('n', 'N')) # Both NAN or both sNAN.
def _calc_errors(actual, expected):
"""Return the absolute and relative errors between two numbers.
>>> _calc_errors(100, 75)
(25, 0.25)
>>> _calc_errors(100, 100)
(0, 0.0)
Returns the (absolute error, relative error) between the two arguments.
"""
base = max(abs(actual), abs(expected))
abs_err = abs(actual - expected)
rel_err = abs_err/base if base else float('inf')
return (abs_err, rel_err)
def approx_equal(x, y, tol=1e-12, rel=1e-7):
"""approx_equal(x, y [, tol [, rel]]) => True|False
Return True if numbers x and y are approximately equal, to within some
margin of error, otherwise return False. Numbers which compare equal
will also compare approximately equal.
x is approximately equal to y if the difference between them is less than
an absolute error tol or a relative error rel, whichever is bigger.
If given, both tol and rel must be finite, non-negative numbers. If not
given, default values are tol=1e-12 and rel=1e-7.
>>> approx_equal(1.2589, 1.2587, tol=0.0003, rel=0)
True
>>> approx_equal(1.2589, 1.2587, tol=0.0001, rel=0)
False
Absolute error is defined as abs(x-y); if that is less than or equal to
tol, x and y are considered approximately equal.
Relative error is defined as abs((x-y)/x) or abs((x-y)/y), whichever is
smaller, provided x or y are not zero. If that figure is less than or
equal to rel, x and y are considered approximately equal.
Complex numbers are not directly supported. If you wish to compare to
complex numbers, extract their real and imaginary parts and compare them
individually.
NANs always compare unequal, even with themselves. Infinities compare
approximately equal if they have the same sign (both positive or both
negative). Infinities with different signs compare unequal; so do
comparisons of infinities with finite numbers.
"""
if tol < 0 or rel < 0:
raise ValueError('error tolerances must be non-negative')
# NANs are never equal to anything, approximately or otherwise.
if math.isnan(x) or math.isnan(y):
return False
# Numbers which compare equal also compare approximately equal.
if x == y:
# This includes the case of two infinities with the same sign.
return True
if math.isinf(x) or math.isinf(y):
# This includes the case of two infinities of opposite sign, or
# one infinity and one finite number.
return False
# Two finite numbers.
actual_error = abs(x - y)
allowed_error = max(tol, rel*max(abs(x), abs(y)))
return actual_error <= allowed_error
# This class exists only as somewhere to stick a docstring containing
# doctests. The following docstring and tests were originally in a separate
# module. Now that it has been merged in here, I need somewhere to hang the.
# docstring. Ultimately, this class will die, and the information below will
# either become redundant, or be moved into more appropriate places.
class _DoNothing:
"""
When doing numeric work, especially with floats, exact equality is often
not what you want. Due to round-off error, it is often a bad idea to try
to compare floats with equality. Instead the usual procedure is to test
them with some (hopefully small!) allowance for error.
The ``approx_equal`` function allows you to specify either an absolute
error tolerance, or a relative error, or both.
Absolute error tolerances are simple, but you need to know the magnitude
of the quantities being compared:
>>> approx_equal(12.345, 12.346, tol=1e-3)
True
>>> approx_equal(12.345e6, 12.346e6, tol=1e-3) # tol is too small.
False
Relative errors are more suitable when the values you are comparing can
vary in magnitude:
>>> approx_equal(12.345, 12.346, rel=1e-4)
True
>>> approx_equal(12.345e6, 12.346e6, rel=1e-4)
True
but a naive implementation of relative error testing can run into trouble
around zero.
If you supply both an absolute tolerance and a relative error, the
comparison succeeds if either individual test succeeds:
>>> approx_equal(12.345e6, 12.346e6, tol=1e-3, rel=1e-4)
True
"""
pass
# We prefer this for testing numeric values that may not be exactly equal,
# and avoid using TestCase.assertAlmostEqual, because it sucks :-)
py_statistics = import_helper.import_fresh_module('statistics',
blocked=['_statistics'])
c_statistics = import_helper.import_fresh_module('statistics',
fresh=['_statistics'])
class TestModules(unittest.TestCase):
func_names = ['_normal_dist_inv_cdf']
def test_py_functions(self):
for fname in self.func_names:
self.assertEqual(getattr(py_statistics, fname).__module__, 'statistics')
@unittest.skipUnless(c_statistics, 'requires _statistics')
def test_c_functions(self):
for fname in self.func_names:
self.assertEqual(getattr(c_statistics, fname).__module__, '_statistics')
class NumericTestCase(unittest.TestCase):
"""Unit test class for numeric work.
This subclasses TestCase. In addition to the standard method
``TestCase.assertAlmostEqual``, ``assertApproxEqual`` is provided.
"""
# By default, we expect exact equality, unless overridden.
tol = rel = 0
def assertApproxEqual(
self, first, second, tol=None, rel=None, msg=None
):
"""Test passes if ``first`` and ``second`` are approximately equal.
This test passes if ``first`` and ``second`` are equal to
within ``tol``, an absolute error, or ``rel``, a relative error.
If either ``tol`` or ``rel`` are None or not given, they default to
test attributes of the same name (by default, 0).
The objects may be either numbers, or sequences of numbers. Sequences
are tested element-by-element.
>>> class MyTest(NumericTestCase):
... def test_number(self):
... x = 1.0/6
... y = sum([x]*6)
... self.assertApproxEqual(y, 1.0, tol=1e-15)
... def test_sequence(self):
... a = [1.001, 1.001e-10, 1.001e10]
... b = [1.0, 1e-10, 1e10]
... self.assertApproxEqual(a, b, rel=1e-3)
...
>>> import unittest
>>> from io import StringIO # Suppress test runner output.
>>> suite = unittest.TestLoader().loadTestsFromTestCase(MyTest)
>>> unittest.TextTestRunner(stream=StringIO()).run(suite)
<unittest.runner.TextTestResult run=2 errors=0 failures=0>
"""
if tol is None:
tol = self.tol
if rel is None:
rel = self.rel
if (
isinstance(first, collections.abc.Sequence) and
isinstance(second, collections.abc.Sequence)
):
check = self._check_approx_seq
else:
check = self._check_approx_num
check(first, second, tol, rel, msg)
def _check_approx_seq(self, first, second, tol, rel, msg):
if len(first) != len(second):
standardMsg = (
"sequences differ in length: %d items != %d items"
% (len(first), len(second))
)
msg = self._formatMessage(msg, standardMsg)
raise self.failureException(msg)
for i, (a,e) in enumerate(zip(first, second)):
self._check_approx_num(a, e, tol, rel, msg, i)
def _check_approx_num(self, first, second, tol, rel, msg, idx=None):
if approx_equal(first, second, tol, rel):
# Test passes. Return early, we are done.
return None
# Otherwise we failed.
standardMsg = self._make_std_err_msg(first, second, tol, rel, idx)
msg = self._formatMessage(msg, standardMsg)
raise self.failureException(msg)
@staticmethod
def _make_std_err_msg(first, second, tol, rel, idx):
# Create the standard error message for approx_equal failures.
assert first != second
template = (
' %r != %r\n'
' values differ by more than tol=%r and rel=%r\n'
' -> absolute error = %r\n'
' -> relative error = %r'
)
if idx is not None:
header = 'numeric sequences first differ at index %d.\n' % idx
template = header + template
# Calculate actual errors:
abs_err, rel_err = _calc_errors(first, second)
return template % (first, second, tol, rel, abs_err, rel_err)
# ========================
# === Test the helpers ===
# ========================
class TestSign(unittest.TestCase):
"""Test that the helper function sign() works correctly."""
def testZeroes(self):
# Test that signed zeroes report their sign correctly.
self.assertEqual(sign(0.0), +1)
self.assertEqual(sign(-0.0), -1)
# --- Tests for approx_equal ---
class ApproxEqualSymmetryTest(unittest.TestCase):
# Test symmetry of approx_equal.
def test_relative_symmetry(self):
# Check that approx_equal treats relative error symmetrically.
# (a-b)/a is usually not equal to (a-b)/b. Ensure that this
# doesn't matter.
#
# Note: the reason for this test is that an early version
# of approx_equal was not symmetric. A relative error test
# would pass, or fail, depending on which value was passed
# as the first argument.
#
args1 = [2456, 37.8, -12.45, Decimal('2.54'), Fraction(17, 54)]
args2 = [2459, 37.2, -12.41, Decimal('2.59'), Fraction(15, 54)]
assert len(args1) == len(args2)
for a, b in zip(args1, args2):
self.do_relative_symmetry(a, b)
def do_relative_symmetry(self, a, b):
a, b = min(a, b), max(a, b)
assert a < b
delta = b - a # The absolute difference between the values.
rel_err1, rel_err2 = abs(delta/a), abs(delta/b)
# Choose an error margin halfway between the two.
rel = (rel_err1 + rel_err2)/2
# Now see that values a and b compare approx equal regardless of
# which is given first.
self.assertTrue(approx_equal(a, b, tol=0, rel=rel))
self.assertTrue(approx_equal(b, a, tol=0, rel=rel))
def test_symmetry(self):
# Test that approx_equal(a, b) == approx_equal(b, a)
args = [-23, -2, 5, 107, 93568]
delta = 2
for a in args:
for type_ in (int, float, Decimal, Fraction):
x = type_(a)*100
y = x + delta
r = abs(delta/max(x, y))
# There are five cases to check:
# 1) actual error <= tol, <= rel
self.do_symmetry_test(x, y, tol=delta, rel=r)
self.do_symmetry_test(x, y, tol=delta+1, rel=2*r)
# 2) actual error > tol, > rel
self.do_symmetry_test(x, y, tol=delta-1, rel=r/2)
# 3) actual error <= tol, > rel
self.do_symmetry_test(x, y, tol=delta, rel=r/2)
# 4) actual error > tol, <= rel
self.do_symmetry_test(x, y, tol=delta-1, rel=r)
self.do_symmetry_test(x, y, tol=delta-1, rel=2*r)
# 5) exact equality test
self.do_symmetry_test(x, x, tol=0, rel=0)
self.do_symmetry_test(x, y, tol=0, rel=0)
def do_symmetry_test(self, a, b, tol, rel):
template = "approx_equal comparisons don't match for %r"
flag1 = approx_equal(a, b, tol, rel)
flag2 = approx_equal(b, a, tol, rel)
self.assertEqual(flag1, flag2, template.format((a, b, tol, rel)))
class ApproxEqualExactTest(unittest.TestCase):
# Test the approx_equal function with exactly equal values.
# Equal values should compare as approximately equal.
# Test cases for exactly equal values, which should compare approx
# equal regardless of the error tolerances given.
def do_exactly_equal_test(self, x, tol, rel):
result = approx_equal(x, x, tol=tol, rel=rel)
self.assertTrue(result, 'equality failure for x=%r' % x)
result = approx_equal(-x, -x, tol=tol, rel=rel)
self.assertTrue(result, 'equality failure for x=%r' % -x)
def test_exactly_equal_ints(self):
# Test that equal int values are exactly equal.
for n in [42, 19740, 14974, 230, 1795, 700245, 36587]:
self.do_exactly_equal_test(n, 0, 0)
def test_exactly_equal_floats(self):
# Test that equal float values are exactly equal.
for x in [0.42, 1.9740, 1497.4, 23.0, 179.5, 70.0245, 36.587]:
self.do_exactly_equal_test(x, 0, 0)
def test_exactly_equal_fractions(self):
# Test that equal Fraction values are exactly equal.
F = Fraction
for f in [F(1, 2), F(0), F(5, 3), F(9, 7), F(35, 36), F(3, 7)]:
self.do_exactly_equal_test(f, 0, 0)
def test_exactly_equal_decimals(self):
# Test that equal Decimal values are exactly equal.
D = Decimal
for d in map(D, "8.2 31.274 912.04 16.745 1.2047".split()):
self.do_exactly_equal_test(d, 0, 0)
def test_exactly_equal_absolute(self):
# Test that equal values are exactly equal with an absolute error.
for n in [16, 1013, 1372, 1198, 971, 4]:
# Test as ints.
self.do_exactly_equal_test(n, 0.01, 0)
# Test as floats.
self.do_exactly_equal_test(n/10, 0.01, 0)
# Test as Fractions.
f = Fraction(n, 1234)
self.do_exactly_equal_test(f, 0.01, 0)
def test_exactly_equal_absolute_decimals(self):
# Test equal Decimal values are exactly equal with an absolute error.
self.do_exactly_equal_test(Decimal("3.571"), Decimal("0.01"), 0)
self.do_exactly_equal_test(-Decimal("81.3971"), Decimal("0.01"), 0)
def test_exactly_equal_relative(self):
# Test that equal values are exactly equal with a relative error.
for x in [8347, 101.3, -7910.28, Fraction(5, 21)]:
self.do_exactly_equal_test(x, 0, 0.01)
self.do_exactly_equal_test(Decimal("11.68"), 0, Decimal("0.01"))
def test_exactly_equal_both(self):
# Test that equal values are equal when both tol and rel are given.
for x in [41017, 16.742, -813.02, Fraction(3, 8)]:
self.do_exactly_equal_test(x, 0.1, 0.01)
D = Decimal
self.do_exactly_equal_test(D("7.2"), D("0.1"), D("0.01"))
class ApproxEqualUnequalTest(unittest.TestCase):
# Unequal values should compare unequal with zero error tolerances.
# Test cases for unequal values, with exact equality test.
def do_exactly_unequal_test(self, x):
for a in (x, -x):
result = approx_equal(a, a+1, tol=0, rel=0)
self.assertFalse(result, 'inequality failure for x=%r' % a)
def test_exactly_unequal_ints(self):
# Test unequal int values are unequal with zero error tolerance.
for n in [951, 572305, 478, 917, 17240]:
self.do_exactly_unequal_test(n)
def test_exactly_unequal_floats(self):
# Test unequal float values are unequal with zero error tolerance.
for x in [9.51, 5723.05, 47.8, 9.17, 17.24]:
self.do_exactly_unequal_test(x)
def test_exactly_unequal_fractions(self):
# Test that unequal Fractions are unequal with zero error tolerance.
F = Fraction
for f in [F(1, 5), F(7, 9), F(12, 11), F(101, 99023)]:
self.do_exactly_unequal_test(f)
def test_exactly_unequal_decimals(self):
# Test that unequal Decimals are unequal with zero error tolerance.
for d in map(Decimal, "3.1415 298.12 3.47 18.996 0.00245".split()):
self.do_exactly_unequal_test(d)
class ApproxEqualInexactTest(unittest.TestCase):
# Inexact test cases for approx_error.
# Test cases when comparing two values that are not exactly equal.
# === Absolute error tests ===
def do_approx_equal_abs_test(self, x, delta):
template = "Test failure for x={!r}, y={!r}"
for y in (x + delta, x - delta):
msg = template.format(x, y)
self.assertTrue(approx_equal(x, y, tol=2*delta, rel=0), msg)
self.assertFalse(approx_equal(x, y, tol=delta/2, rel=0), msg)
def test_approx_equal_absolute_ints(self):
# Test approximate equality of ints with an absolute error.
for n in [-10737, -1975, -7, -2, 0, 1, 9, 37, 423, 9874, 23789110]:
self.do_approx_equal_abs_test(n, 10)
self.do_approx_equal_abs_test(n, 2)
def test_approx_equal_absolute_floats(self):
# Test approximate equality of floats with an absolute error.
for x in [-284.126, -97.1, -3.4, -2.15, 0.5, 1.0, 7.8, 4.23, 3817.4]:
self.do_approx_equal_abs_test(x, 1.5)
self.do_approx_equal_abs_test(x, 0.01)
self.do_approx_equal_abs_test(x, 0.0001)
def test_approx_equal_absolute_fractions(self):
# Test approximate equality of Fractions with an absolute error.
delta = Fraction(1, 29)
numerators = [-84, -15, -2, -1, 0, 1, 5, 17, 23, 34, 71]
for f in (Fraction(n, 29) for n in numerators):
self.do_approx_equal_abs_test(f, delta)
self.do_approx_equal_abs_test(f, float(delta))
def test_approx_equal_absolute_decimals(self):
# Test approximate equality of Decimals with an absolute error.
delta = Decimal("0.01")
for d in map(Decimal, "1.0 3.5 36.08 61.79 7912.3648".split()):
self.do_approx_equal_abs_test(d, delta)
self.do_approx_equal_abs_test(-d, delta)
def test_cross_zero(self):
# Test for the case of the two values having opposite signs.
self.assertTrue(approx_equal(1e-5, -1e-5, tol=1e-4, rel=0))
# === Relative error tests ===
def do_approx_equal_rel_test(self, x, delta):
template = "Test failure for x={!r}, y={!r}"
for y in (x*(1+delta), x*(1-delta)):
msg = template.format(x, y)
self.assertTrue(approx_equal(x, y, tol=0, rel=2*delta), msg)
self.assertFalse(approx_equal(x, y, tol=0, rel=delta/2), msg)
def test_approx_equal_relative_ints(self):
# Test approximate equality of ints with a relative error.
self.assertTrue(approx_equal(64, 47, tol=0, rel=0.36))
self.assertTrue(approx_equal(64, 47, tol=0, rel=0.37))
# ---
self.assertTrue(approx_equal(449, 512, tol=0, rel=0.125))
self.assertTrue(approx_equal(448, 512, tol=0, rel=0.125))
self.assertFalse(approx_equal(447, 512, tol=0, rel=0.125))
def test_approx_equal_relative_floats(self):
# Test approximate equality of floats with a relative error.
for x in [-178.34, -0.1, 0.1, 1.0, 36.97, 2847.136, 9145.074]:
self.do_approx_equal_rel_test(x, 0.02)
self.do_approx_equal_rel_test(x, 0.0001)
def test_approx_equal_relative_fractions(self):
# Test approximate equality of Fractions with a relative error.
F = Fraction
delta = Fraction(3, 8)
for f in [F(3, 84), F(17, 30), F(49, 50), F(92, 85)]:
for d in (delta, float(delta)):
self.do_approx_equal_rel_test(f, d)
self.do_approx_equal_rel_test(-f, d)
def test_approx_equal_relative_decimals(self):
# Test approximate equality of Decimals with a relative error.
for d in map(Decimal, "0.02 1.0 5.7 13.67 94.138 91027.9321".split()):
self.do_approx_equal_rel_test(d, Decimal("0.001"))
self.do_approx_equal_rel_test(-d, Decimal("0.05"))
# === Both absolute and relative error tests ===
# There are four cases to consider:
# 1) actual error <= both absolute and relative error
# 2) actual error <= absolute error but > relative error
# 3) actual error <= relative error but > absolute error
# 4) actual error > both absolute and relative error
def do_check_both(self, a, b, tol, rel, tol_flag, rel_flag):
check = self.assertTrue if tol_flag else self.assertFalse
check(approx_equal(a, b, tol=tol, rel=0))
check = self.assertTrue if rel_flag else self.assertFalse
check(approx_equal(a, b, tol=0, rel=rel))
check = self.assertTrue if (tol_flag or rel_flag) else self.assertFalse
check(approx_equal(a, b, tol=tol, rel=rel))
def test_approx_equal_both1(self):
# Test actual error <= both absolute and relative error.
self.do_check_both(7.955, 7.952, 0.004, 3.8e-4, True, True)
self.do_check_both(-7.387, -7.386, 0.002, 0.0002, True, True)
def test_approx_equal_both2(self):
# Test actual error <= absolute error but > relative error.
self.do_check_both(7.955, 7.952, 0.004, 3.7e-4, True, False)
def test_approx_equal_both3(self):
# Test actual error <= relative error but > absolute error.
self.do_check_both(7.955, 7.952, 0.001, 3.8e-4, False, True)
def test_approx_equal_both4(self):
# Test actual error > both absolute and relative error.
self.do_check_both(2.78, 2.75, 0.01, 0.001, False, False)
self.do_check_both(971.44, 971.47, 0.02, 3e-5, False, False)
class ApproxEqualSpecialsTest(unittest.TestCase):
# Test approx_equal with NANs and INFs and zeroes.
def test_inf(self):
for type_ in (float, Decimal):
inf = type_('inf')
self.assertTrue(approx_equal(inf, inf))
self.assertTrue(approx_equal(inf, inf, 0, 0))
self.assertTrue(approx_equal(inf, inf, 1, 0.01))
self.assertTrue(approx_equal(-inf, -inf))
self.assertFalse(approx_equal(inf, -inf))
self.assertFalse(approx_equal(inf, 1000))
def test_nan(self):
for type_ in (float, Decimal):
nan = type_('nan')
for other in (nan, type_('inf'), 1000):
self.assertFalse(approx_equal(nan, other))
def test_float_zeroes(self):
nzero = math.copysign(0.0, -1)
self.assertTrue(approx_equal(nzero, 0.0, tol=0.1, rel=0.1))
def test_decimal_zeroes(self):
nzero = Decimal("-0.0")
self.assertTrue(approx_equal(nzero, Decimal(0), tol=0.1, rel=0.1))
class TestApproxEqualErrors(unittest.TestCase):
# Test error conditions of approx_equal.
def test_bad_tol(self):
# Test negative tol raises.
self.assertRaises(ValueError, approx_equal, 100, 100, -1, 0.1)
def test_bad_rel(self):
# Test negative rel raises.
self.assertRaises(ValueError, approx_equal, 100, 100, 1, -0.1)
# --- Tests for NumericTestCase ---
# The formatting routine that generates the error messages is complex enough
# that it too needs testing.
class TestNumericTestCase(unittest.TestCase):
# The exact wording of NumericTestCase error messages is *not* guaranteed,
# but we need to give them some sort of test to ensure that they are
# generated correctly. As a compromise, we look for specific substrings
# that are expected to be found even if the overall error message changes.
def do_test(self, args):
actual_msg = NumericTestCase._make_std_err_msg(*args)
expected = self.generate_substrings(*args)
for substring in expected:
self.assertIn(substring, actual_msg)
def test_numerictestcase_is_testcase(self):
# Ensure that NumericTestCase actually is a TestCase.
self.assertTrue(issubclass(NumericTestCase, unittest.TestCase))
def test_error_msg_numeric(self):
# Test the error message generated for numeric comparisons.
args = (2.5, 4.0, 0.5, 0.25, None)
self.do_test(args)
def test_error_msg_sequence(self):
# Test the error message generated for sequence comparisons.
args = (3.75, 8.25, 1.25, 0.5, 7)
self.do_test(args)
def generate_substrings(self, first, second, tol, rel, idx):
"""Return substrings we expect to see in error messages."""
abs_err, rel_err = _calc_errors(first, second)
substrings = [
'tol=%r' % tol,
'rel=%r' % rel,
'absolute error = %r' % abs_err,
'relative error = %r' % rel_err,
]
if idx is not None:
substrings.append('differ at index %d' % idx)
return substrings
# =======================================
# === Tests for the statistics module ===
# =======================================
class GlobalsTest(unittest.TestCase):
module = statistics
expected_metadata = ["__doc__", "__all__"]
def test_meta(self):
# Test for the existence of metadata.
for meta in self.expected_metadata:
self.assertTrue(hasattr(self.module, meta),
"%s not present" % meta)
def test_check_all(self):
# Check everything in __all__ exists and is public.
module = self.module
for name in module.__all__:
# No private names in __all__:
self.assertFalse(name.startswith("_"),
'private name "%s" in __all__' % name)
# And anything in __all__ must exist:
self.assertTrue(hasattr(module, name),
'missing name "%s" in __all__' % name)
class StatisticsErrorTest(unittest.TestCase):
def test_has_exception(self):
errmsg = (
"Expected StatisticsError to be a ValueError, but got a"
" subclass of %r instead."
)
self.assertTrue(hasattr(statistics, 'StatisticsError'))
self.assertTrue(
issubclass(statistics.StatisticsError, ValueError),
errmsg % statistics.StatisticsError.__base__
)
# === Tests for private utility functions ===
class ExactRatioTest(unittest.TestCase):
# Test _exact_ratio utility.
def test_int(self):
for i in (-20, -3, 0, 5, 99, 10**20):
self.assertEqual(statistics._exact_ratio(i), (i, 1))
def test_fraction(self):
numerators = (-5, 1, 12, 38)
for n in numerators:
f = Fraction(n, 37)
self.assertEqual(statistics._exact_ratio(f), (n, 37))
def test_float(self):
self.assertEqual(statistics._exact_ratio(0.125), (1, 8))
self.assertEqual(statistics._exact_ratio(1.125), (9, 8))
data = [random.uniform(-100, 100) for _ in range(100)]
for x in data:
num, den = statistics._exact_ratio(x)
self.assertEqual(x, num/den)
def test_decimal(self):
D = Decimal
_exact_ratio = statistics._exact_ratio
self.assertEqual(_exact_ratio(D("0.125")), (1, 8))
self.assertEqual(_exact_ratio(D("12.345")), (2469, 200))
self.assertEqual(_exact_ratio(D("-1.98")), (-99, 50))
def test_inf(self):
INF = float("INF")
class MyFloat(float):
pass
class MyDecimal(Decimal):
pass
for inf in (INF, -INF):
for type_ in (float, MyFloat, Decimal, MyDecimal):
x = type_(inf)
ratio = statistics._exact_ratio(x)
self.assertEqual(ratio, (x, None))
self.assertEqual(type(ratio[0]), type_)
self.assertTrue(math.isinf(ratio[0]))
def test_float_nan(self):
NAN = float("NAN")
class MyFloat(float):
pass
for nan in (NAN, MyFloat(NAN)):
ratio = statistics._exact_ratio(nan)
self.assertTrue(math.isnan(ratio[0]))
self.assertIs(ratio[1], None)
self.assertEqual(type(ratio[0]), type(nan))
def test_decimal_nan(self):
NAN = Decimal("NAN")
sNAN = Decimal("sNAN")
class MyDecimal(Decimal):
pass
for nan in (NAN, MyDecimal(NAN), sNAN, MyDecimal(sNAN)):
ratio = statistics._exact_ratio(nan)
self.assertTrue(_nan_equal(ratio[0], nan))
self.assertIs(ratio[1], None)
self.assertEqual(type(ratio[0]), type(nan))
class DecimalToRatioTest(unittest.TestCase):
# Test _exact_ratio private function.
def test_infinity(self):
# Test that INFs are handled correctly.
inf = Decimal('INF')
self.assertEqual(statistics._exact_ratio(inf), (inf, None))
self.assertEqual(statistics._exact_ratio(-inf), (-inf, None))
def test_nan(self):
# Test that NANs are handled correctly.
for nan in (Decimal('NAN'), Decimal('sNAN')):
num, den = statistics._exact_ratio(nan)
# Because NANs always compare non-equal, we cannot use assertEqual.
# Nor can we use an identity test, as we don't guarantee anything
# about the object identity.
self.assertTrue(_nan_equal(num, nan))
self.assertIs(den, None)
def test_sign(self):
# Test sign is calculated correctly.
numbers = [Decimal("9.8765e12"), Decimal("9.8765e-12")]
for d in numbers:
# First test positive decimals.
assert d > 0
num, den = statistics._exact_ratio(d)
self.assertGreaterEqual(num, 0)
self.assertGreater(den, 0)
# Then test negative decimals.
num, den = statistics._exact_ratio(-d)
self.assertLessEqual(num, 0)
self.assertGreater(den, 0)
def test_negative_exponent(self):
# Test result when the exponent is negative.
t = statistics._exact_ratio(Decimal("0.1234"))
self.assertEqual(t, (617, 5000))
def test_positive_exponent(self):
# Test results when the exponent is positive.
t = statistics._exact_ratio(Decimal("1.234e7"))
self.assertEqual(t, (12340000, 1))
def test_regression_20536(self):
# Regression test for issue 20536.
# See http://bugs.python.org/issue20536
t = statistics._exact_ratio(Decimal("1e2"))
self.assertEqual(t, (100, 1))
t = statistics._exact_ratio(Decimal("1.47e5"))
self.assertEqual(t, (147000, 1))
class IsFiniteTest(unittest.TestCase):
# Test _isfinite private function.
def test_finite(self):
# Test that finite numbers are recognised as finite.
for x in (5, Fraction(1, 3), 2.5, Decimal("5.5")):
self.assertTrue(statistics._isfinite(x))
def test_infinity(self):
# Test that INFs are not recognised as finite.
for x in (float("inf"), Decimal("inf")):
self.assertFalse(statistics._isfinite(x))
def test_nan(self):
# Test that NANs are not recognised as finite.
for x in (float("nan"), Decimal("NAN"), Decimal("sNAN")):
self.assertFalse(statistics._isfinite(x))
class CoerceTest(unittest.TestCase):
# Test that private function _coerce correctly deals with types.
# The coercion rules are currently an implementation detail, although at
# some point that should change. The tests and comments here define the
# correct implementation.
# Pre-conditions of _coerce:
#
# - The first time _sum calls _coerce, the
# - coerce(T, S) will never be called with bool as the first argument;
# this is a pre-condition, guarded with an assertion.
#
# - coerce(T, T) will always return T; we assume T is a valid numeric
# type. Violate this assumption at your own risk.
#
# - Apart from as above, bool is treated as if it were actually int.
#
# - coerce(int, X) and coerce(X, int) return X.
# -
def test_bool(self):
# bool is somewhat special, due to the pre-condition that it is
# never given as the first argument to _coerce, and that it cannot
# be subclassed. So we test it specially.
for T in (int, float, Fraction, Decimal):
self.assertIs(statistics._coerce(T, bool), T)
class MyClass(T): pass
self.assertIs(statistics._coerce(MyClass, bool), MyClass)
def assertCoerceTo(self, A, B):
"""Assert that type A coerces to B."""
self.assertIs(statistics._coerce(A, B), B)
self.assertIs(statistics._coerce(B, A), B)
def check_coerce_to(self, A, B):
"""Checks that type A coerces to B, including subclasses."""
# Assert that type A is coerced to B.
self.assertCoerceTo(A, B)
# Subclasses of A are also coerced to B.
class SubclassOfA(A): pass
self.assertCoerceTo(SubclassOfA, B)
# A, and subclasses of A, are coerced to subclasses of B.
class SubclassOfB(B): pass
self.assertCoerceTo(A, SubclassOfB)
self.assertCoerceTo(SubclassOfA, SubclassOfB)
def assertCoerceRaises(self, A, B):
"""Assert that coercing A to B, or vice versa, raises TypeError."""
self.assertRaises(TypeError, statistics._coerce, (A, B))
self.assertRaises(TypeError, statistics._coerce, (B, A))
def check_type_coercions(self, T):
"""Check that type T coerces correctly with subclasses of itself."""
assert T is not bool
# Coercing a type with itself returns the same type.
self.assertIs(statistics._coerce(T, T), T)
# Coercing a type with a subclass of itself returns the subclass.
class U(T): pass
class V(T): pass
class W(U): pass
for typ in (U, V, W):
self.assertCoerceTo(T, typ)
self.assertCoerceTo(U, W)
# Coercing two subclasses that aren't parent/child is an error.
self.assertCoerceRaises(U, V)
self.assertCoerceRaises(V, W)
def test_int(self):
# Check that int coerces correctly.
self.check_type_coercions(int)
for typ in (float, Fraction, Decimal):
self.check_coerce_to(int, typ)
def test_fraction(self):
# Check that Fraction coerces correctly.
self.check_type_coercions(Fraction)
self.check_coerce_to(Fraction, float)
def test_decimal(self):
# Check that Decimal coerces correctly.
self.check_type_coercions(Decimal)
def test_float(self):
# Check that float coerces correctly.
self.check_type_coercions(float)
def test_non_numeric_types(self):
for bad_type in (str, list, type(None), tuple, dict):
for good_type in (int, float, Fraction, Decimal):
self.assertCoerceRaises(good_type, bad_type)
def test_incompatible_types(self):
# Test that incompatible types raise.
for T in (float, Fraction):
class MySubclass(T): pass
self.assertCoerceRaises(T, Decimal)
self.assertCoerceRaises(MySubclass, Decimal)
class ConvertTest(unittest.TestCase):
# Test private _convert function.
def check_exact_equal(self, x, y):
"""Check that x equals y, and has the same type as well."""
self.assertEqual(x, y)
self.assertIs(type(x), type(y))
def test_int(self):
# Test conversions to int.
x = statistics._convert(Fraction(71), int)
self.check_exact_equal(x, 71)
class MyInt(int): pass
x = statistics._convert(Fraction(17), MyInt)
self.check_exact_equal(x, MyInt(17))
def test_fraction(self):
# Test conversions to Fraction.
x = statistics._convert(Fraction(95, 99), Fraction)
self.check_exact_equal(x, Fraction(95, 99))
class MyFraction(Fraction):
def __truediv__(self, other):
return self.__class__(super().__truediv__(other))
x = statistics._convert(Fraction(71, 13), MyFraction)
self.check_exact_equal(x, MyFraction(71, 13))
def test_float(self):
# Test conversions to float.
x = statistics._convert(Fraction(-1, 2), float)
self.check_exact_equal(x, -0.5)
class MyFloat(float):
def __truediv__(self, other):
return self.__class__(super().__truediv__(other))
x = statistics._convert(Fraction(9, 8), MyFloat)
self.check_exact_equal(x, MyFloat(1.125))
def test_decimal(self):
# Test conversions to Decimal.
x = statistics._convert(Fraction(1, 40), Decimal)
self.check_exact_equal(x, Decimal("0.025"))
class MyDecimal(Decimal):
def __truediv__(self, other):
return self.__class__(super().__truediv__(other))
x = statistics._convert(Fraction(-15, 16), MyDecimal)
self.check_exact_equal(x, MyDecimal("-0.9375"))
def test_inf(self):
for INF in (float('inf'), Decimal('inf')):
for inf in (INF, -INF):
x = statistics._convert(inf, type(inf))