Skip to content

Commit 7a624bf

Browse files
committed
bpo-35588: Implement mod and divmod operations for Fraction type by spelling out the numerator/denominator calculation, instead of instantiating and normalising Fractions along the way. This speeds up '%' and divmod() by 2-3x.
1 parent 32d96a2 commit 7a624bf

File tree

3 files changed

+41
-3
lines changed

3 files changed

+41
-3
lines changed

Lib/fractions.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,17 @@ def _gcd(a, b):
3535
a, b = b, a%b
3636
return a
3737

38+
39+
def _flat_divmod(na, da, nb, db):
40+
"""(a // b, a % b)"""
41+
# div = a // b
42+
n_div, d_div = na * db, da * nb
43+
div = n_div // d_div
44+
# mod = a - b * div == (na*db - da*nb * div) / (da*db)
45+
n_mod, d_mod = n_div - d_div * div, da*db
46+
return div, n_mod, d_mod
47+
48+
3849
# Constants related to the hash implementation; hash(x) is based
3950
# on the reduction of x modulo the prime _PyHASH_MODULUS.
4051
_PyHASH_MODULUS = sys.hash_info.modulus
@@ -433,10 +444,19 @@ def _floordiv(a, b):
433444

434445
__floordiv__, __rfloordiv__ = _operator_fallbacks(_floordiv, operator.floordiv)
435446

447+
def _divmod(a, b):
448+
"""(a // b, a % b)"""
449+
div, n_mod, d_mod = _flat_divmod(
450+
a.numerator, a.denominator, b.numerator, b.denominator)
451+
return Fraction(div), Fraction(n_mod, d_mod)
452+
453+
__divmod__, __rdivmod__ = _operator_fallbacks(_divmod, divmod)
454+
436455
def _mod(a, b):
437456
"""a % b"""
438-
div = a // b
439-
return a - b * div
457+
_, n_mod, d_mod = _flat_divmod(
458+
a.numerator, a.denominator, b.numerator, b.denominator)
459+
return Fraction(n_mod, d_mod)
440460

441461
__mod__, __rmod__ = _operator_fallbacks(_mod, operator.mod)
442462

Lib/test/test_fractions.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ def assertTypedEquals(self, expected, actual):
117117
self.assertEqual(type(expected), type(actual))
118118
self.assertEqual(expected, actual)
119119

120+
def assertTypedTupleEquals(self, expected, actual):
121+
"""Asserts that both the types and values in the tuples are the same."""
122+
self.assertIsInstance(actual, tuple)
123+
self.assertEqual(list(map(type, expected)), list(map(type, actual)))
124+
self.assertEqual(expected, actual)
125+
120126
def assertRaisesMessage(self, exc_type, message,
121127
callable, *args, **kwargs):
122128
"""Asserts that callable(*args, **kwargs) raises exc_type(message)."""
@@ -349,7 +355,10 @@ def testArithmetic(self):
349355
self.assertEqual(F(1, 4), F(1, 10) / F(2, 5))
350356
self.assertTypedEquals(2, F(9, 10) // F(2, 5))
351357
self.assertTypedEquals(10**23, F(10**23, 1) // F(1))
358+
self.assertEqual(F(5, 6), F(7, 3) % F(3, 2))
352359
self.assertEqual(F(2, 3), F(-7, 3) % F(3, 2))
360+
self.assertEqual((F(1), F(5, 6)), divmod(F(7, 3), F(3, 2)))
361+
self.assertEqual((F(-2), F(2, 3)), divmod(F(-7, 3), F(3, 2)))
353362
self.assertEqual(F(8, 27), F(2, 3) ** F(3))
354363
self.assertEqual(F(27, 8), F(2, 3) ** F(-3))
355364
self.assertTypedEquals(2.0, F(4) ** F(1, 2))
@@ -415,7 +424,14 @@ def testMixedArithmetic(self):
415424
self.assertTypedEquals(float('inf'), F(-1, 10) % float('inf'))
416425
self.assertTypedEquals(-0.1, F(-1, 10) % float('-inf'))
417426

418-
# No need for divmod since we don't override it.
427+
self.assertTypedTupleEquals((F(0), F(1, 10)), divmod(F(1, 10), 1))
428+
self.assertTypedTupleEquals(divmod(0.1, 1.0), divmod(F(1, 10), 1.0))
429+
self.assertTypedTupleEquals((F(10, 1), F(0)), divmod(1, F(1, 10)))
430+
self.assertTypedTupleEquals(divmod(1.0, 0.1), divmod(1.0, F(1, 10)))
431+
self.assertTypedTupleEquals(divmod(0.1, float('inf')), divmod(F(1, 10), float('inf')))
432+
self.assertTypedTupleEquals(divmod(0.1, float('-inf')), divmod(F(1, 10), float('-inf')))
433+
self.assertTypedTupleEquals(divmod(-0.1, float('inf')), divmod(F(-1, 10), float('inf')))
434+
self.assertTypedTupleEquals(divmod(-0.1, float('-inf')), divmod(F(-1, 10), float('-inf')))
419435

420436
# ** has more interesting conversion rules.
421437
self.assertTypedEquals(F(100, 1), F(1, 10) ** -2)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Mod and divmod operations on :class:`fractions.Fraction` types are 2-3x faster.
2+
Patch by Stefan Behnel.

0 commit comments

Comments
 (0)