44from .fixtures import session , testset
55
66from operator import mul
7- from math import exp
7+ from math import exp , trunc , floor , ceil
8+ from sys import float_info
89
910from ..mathseq import (s , m , mg ,
1011 sadd , smul , spow , cauchyprod ,
@@ -51,9 +52,26 @@ def runtests():
5152 test [not almosteq ("ab" , "abc" )]
5253
5354 test [almosteq (1.0 , 1.0 + ulp (1.0 ))]
54- test [almosteq (1e-309 , 0 , tol = 1.0 )]
5555
56- # explicitly listed elements, same as a genexpr using tuple input
56+ # TODO: counterintuitively, need a large tolerance here, because when one operand is zero,
57+ # TODO: the final tolerance is actually tol*min_normal.
58+ min_normal = float_info .min
59+ test [almosteq (min_normal / 2 , 0 , tol = 1.0 )]
60+
61+ too_large = 2 ** int (1e6 )
62+ test_raises [OverflowError , float (too_large ), "UPDATE THIS, need a float overflow here." ]
63+ test [almosteq (too_large , too_large + 1 )] # works, because 1/too_large is very small.
64+
65+ try :
66+ from mpmath import mpf
67+ except ImportError :
68+ error ["mpmath not installed in this Python, cannot test arbitrary precision input for mathseq." ]
69+ else :
70+ test [almosteq (mpf (1.0 ), mpf (1.0 + ulp (1.0 )))]
71+ test [almosteq (1.0 , mpf (1.0 + ulp (1.0 )))]
72+ test [almosteq (mpf (1.0 ), 1.0 + ulp (1.0 ))]
73+
74+ # explicitly listed elements, same as a genexpr using tuple input
5775 with testset ("s, convenience" ):
5876 test [tuple (s (1 )) == (1 ,)]
5977 test [tuple (s (1 , 2 , 3 , 4 , 5 )) == (1 , 2 , 3 , 4 , 5 )]
@@ -62,6 +80,9 @@ def runtests():
6280 # always infinite length, because final element cannot be used to deduce length.
6381 with testset ("s, constant sequence" ):
6482 test [tuple (take (10 , s (1 , ...))) == (1 ,) * 10 ]
83+ # exercise the different branches of the analyzer
84+ test [tuple (take (10 , s (1 , 1 , ...))) == (1 ,) * 10 ]
85+ test [tuple (take (10 , s (1 , 1 , 1 , ...))) == (1 ,) * 10 ]
6586 # type stability (not that it does us any good in Python)
6687 test [all (isinstance (x , int ) for x in take (10 , s (1 , ...)))]
6788 test [all (isinstance (x , float ) for x in take (10 , s (1.0 , ...)))]
@@ -70,6 +91,13 @@ def runtests():
7091 with testset ("s, arithmetic sequence" ):
7192 test [tuple (take (10 , s (1 , 2 , ...))) == tuple (range (1 , 11 ))] # two elements is enough
7293 test [tuple (take (10 , s (1 , 2 , 3 , ...))) == tuple (range (1 , 11 ))] # more is allowed if consistent
94+ # Trigger the analyzer corner case where consecutive differences are almost, but not exactly the same.
95+ # 1/3 is a useful constant here, as it has no exact float representation at any finite number of bits.
96+ # That's also a source of party tricks such as 7/3 - 4/3 - 1 = machine epsilon (in IEEE-754).
97+ # (Expand that in binary to see why.)
98+ # Here the 2/3 term is one ulp off from the "exact" result (which has only representation error).
99+ test [all (abs (x - y ) <= min (ulp (x ), ulp (y )) for x , y in zip (tuple (take (5 , s (1 / 3 , 2 / 3 , 3 / 3 , ...))),
100+ (1 / 3 , 2 / 3 , 3 / 3 , 4 / 3 , 5 / 3 )))]
73101 # type stability
74102 test [all (isinstance (x , int ) for x in take (10 , s (1 , 3 , ...)))]
75103 test [all (isinstance (x , int ) for x in take (10 , s (1 , 3 , 5 , ...)))]
@@ -149,6 +177,13 @@ def runtests():
149177 test [tuple (cauchyprod ((2 , 4 ), (1 , 3 , 5 ), require = "all" )) == (2 , 10 )]
150178 test [tuple (cauchyprod ((2 ,), (1 , 3 , 5 ), require = "all" )) == (2 ,)]
151179
180+ # both inputs must be iterables for this operation to be defined
181+ test_raises [TypeError , cauchyprod (1 , 2 )]
182+ test_raises [TypeError , cauchyprod (s (1 , 3 , 5 , ...), 2 )]
183+ test_raises [TypeError , cauchyprod (2 , s (1 , 3 , 5 , ...))]
184+ # The optional "require" argument must be "all" or "any".
185+ test_raises [ValueError , cauchyprod (s (1 , 3 , 5 , ...), s (2 , 4 , 6 , ...), require = "invalid_value" )]
186+
152187 with testset ("m, mg (infix syntax for arithmetic)" ):
153188 # Sequences returned by `s` are `m`'d implicitly.
154189 test [tuple (take (5 , s (1 , 3 , 5 , ...) + s (2 , 4 , 6 , ...))) == (3 , 7 , 11 , 15 , 19 )]
@@ -167,6 +202,48 @@ def runtests():
167202 test [tuple (take (5 , s (1 , 3 , ...)** 2 )) == (1 , 3 ** 2 , 5 ** 2 , 7 ** 2 , 9 ** 2 )]
168203 test [tuple (take (5 , 2 ** s (1 , 3 , ...))) == (2 ** 1 , 2 ** 3 , 2 ** 5 , 2 ** 7 , 2 ** 9 )]
169204
205+ test [tuple (take (5 , abs (s (- 1 , - 2 , ...)))) == (1 , 2 , 3 , 4 , 5 )] # m.__abs__
206+ test [tuple (take (5 , + s (- 1 , - 2 , ...))) == (- 1 , - 2 , - 3 , - 4 , - 5 )] # m.__pos__
207+ test [tuple (take (5 , - s (- 1 , - 2 , ...))) == (1 , 2 , 3 , 4 , 5 )] # m.__neg__
208+ test [tuple (take (5 , s (1 , 2 , ...) // 2 )) == (0 , 1 , 1 , 2 , 2 )]
209+ test [tuple (take (5 , 10 // s (1 , 2 , ...))) == (10 , 5 , 3 , 2 , 2 )]
210+ test [tuple (take (5 , s (1 , 2 , ...) % 2 )) == (1 , 0 , 1 , 0 , 1 )]
211+ test [tuple (take (5 , 10 % s (1 , 2 , ...))) == (0 , 0 , 1 , 2 , 0 )]
212+ test [tuple (take (5 , divmod (s (1 , 2 , ...), 2 ))) == ((0 , 1 ), (1 , 0 ), (1 , 1 ), (2 , 0 ), (2 , 1 ))]
213+ test [tuple (take (5 , divmod (10 , s (1 , 2 , ...)))) == ((10 , 0 ), (5 , 0 ), (3 , 1 ), (2 , 2 ), (2 , 0 ))]
214+ test [tuple (take (5 , round (s (1.111 , 2.222 , ...)))) == (1 , 2 , 3 , 4 , 6 )]
215+ # But be careful. As the language reference warns:
216+ # https://docs.python.org/3/library/functions.html#round
217+ # rounding is correct taking into account the float representation, which is base-2.
218+ test [tuple (take (5 , round (s (1.111 , 2.222 , ...), 2 ))) == (1.11 , 2.22 , 3.33 , 4.44 , 5.55 )]
219+
220+ test [tuple (take (5 , trunc (s (1.111 , 2.222 , ...)))) == (1 , 2 , 3 , 4 , 5 )]
221+ test [tuple (take (5 , floor (s (1.111 , 2.222 , ...)))) == (1 , 2 , 3 , 4 , 5 )]
222+ test [tuple (take (5 , ceil (s (1.111 , 2.222 , ...)))) == (2 , 3 , 4 , 5 , 6 )]
223+
224+ # bit shifts
225+ test [tuple (take (5 , s (1 , 2 , 4 , ...) << 1 )) == (2 , 4 , 8 , 16 , 32 )]
226+ test [tuple (take (5 , 1 << s (1 , 2 , ...))) == (2 , 4 , 8 , 16 , 32 )]
227+ test [tuple (take (5 , s (2 , 4 , 8 , ...) >> 1 )) == (1 , 2 , 4 , 8 , 16 )]
228+ test [tuple (take (5 , 32 >> s (1 , 2 , ...))) == (16 , 8 , 4 , 2 , 1 )]
229+
230+ # termwise bitwise logical operations
231+ test [tuple (m ((0 , 1 , 0 , 1 )) & m ((0 , 0 , 1 , 1 ))) == (0 , 0 , 0 , 1 )]
232+ test [tuple (m ((0 , 1 , 0 , 1 )) | m ((0 , 0 , 1 , 1 ))) == (0 , 1 , 1 , 1 )]
233+ test [tuple (m ((0 , 1 , 0 , 1 )) ^ m ((0 , 0 , 1 , 1 ))) == (0 , 1 , 1 , 0 )] # xor
234+ test [tuple (1 & m ((0 , 1 ))) == (0 , 1 )]
235+ test [tuple (1 | m ((0 , 1 ))) == (1 , 1 )]
236+ test [tuple (1 ^ m ((0 , 1 ))) == (1 , 0 )]
237+ test [tuple (take (5 , ~ s (1 , 2 , ...))) == (- 2 , - 3 , - 4 , - 5 , - 6 )] # m.__invert__
238+
239+ # v0.14.3+: termwise comparison
240+ test [tuple (s (1 , 2 , 3 ) < s (2 , 3 , 4 )) == (True , True , True )]
241+ test [tuple (s (1 , 2 , 3 ) <= s (1 , 2 , 4 )) == (True , True , True )]
242+ test [tuple (s (1 , 2 , 3 ) == s (3 , 2 , 1 )) == (False , True , False )]
243+ test [tuple (s (1 , 2 , 3 ) != s (3 , 2 , 1 )) == (True , False , True )]
244+ test [tuple (s (1 , 2 , 3 ) >= s (2 , 3 , 4 )) == (False , False , False )]
245+ test [tuple (s (1 , 2 , 3 ) > s (1 , 2 , 4 )) == (False , False , False )]
246+
170247 a = s (1 , 3 , ...)
171248 b = s (2 , 4 , ...)
172249 c = a + b
@@ -208,6 +285,12 @@ def runtests():
208285 test [abs (last (s (0.01 , 0.02 , ..., 10000 )) - 10000.0 ) <= ulp (10000.0 )]
209286
210287 with testset ("error cases" ):
288+ # invalid specifications
289+ test_raises [SyntaxError , s (...)] # no data to work with
290+ test_raises [SyntaxError , s (..., 1 )] # no initial term, ellipsis
291+ test_raises [SyntaxError , s (..., 1 , 2 )] # no initial term, multiple terms after the ellipsis
292+ test_raises [SyntaxError , s (0 , ..., 2 , 3 )] # multiple terms after the ellipsis
293+
211294 test_raises [SyntaxError ,
212295 s (1 , ..., 1 ),
213296 "should detect that the length of a constant sequence cannot be determined from a final element" ]
@@ -220,6 +303,15 @@ def runtests():
220303 test_raises [SyntaxError ,
221304 s (2 , 4 , 0 , ...),
222305 "should detect that a geometric sequence must have no zero elements" ]
306+ test_raises [SyntaxError ,
307+ s (2 , - 4 , 8 , ..., - 32 ),
308+ "should detect that the parity of the last term of alternating geometric sequence is wrong" ]
309+ test_raises [SyntaxError ,
310+ s (2 , - 1 / 4 , 16 , ..., - 65536 ),
311+ "should detect that the parity of the last term of alternating power sequence is wrong" ]
312+ test_raises [SyntaxError ,
313+ s (0 , 1 , 2 , 4 , ...),
314+ "should detect two incompatible sequence types (arith 0, 1, 2; geom 1, 2, 4)" ]
223315 test_raises [SyntaxError ,
224316 s (2 , 3 , 5 , 7 , 11 , ...),
225317 "should detect that s() is not that smart!" ]
@@ -263,6 +355,10 @@ def runtests():
263355 test [tuple (take (10 , primes ())) == (2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 , 23 , 29 )]
264356 test [tuple (take (10 , fibonacci ())) == (1 , 1 , 2 , 3 , 5 , 8 , 13 , 21 , 34 , 55 )]
265357
358+ test [tuple (take (10 , primes (optimize = "speed" ))) == (2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 , 23 , 29 )]
359+ test [tuple (take (10 , primes (optimize = "memory" ))) == (2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 , 23 , 29 )]
360+ test_raises [ValueError , primes (optimize = "fun" )] # only "speed" and "memory" modes exist
361+
266362 factorials = imemoize (scanl (mul , 1 , s (1 , 2 , ...))) # 0!, 1!, 2!, ...
267363 test [last (take (6 , factorials ())) == 120 ]
268364
0 commit comments