Skip to content

gh-72902: speedup Fraction.from_decimal/float in typical cases #133251

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

skirpichev
Copy link
Member

@skirpichev skirpichev commented May 1, 2025

@skirpichev
Copy link
Member Author

Benchmark ref patch patch2
Fraction.from_decimal(1) 15.5 us 6.59 us: 2.35x faster 10.4 us: 1.49x faster
Fraction.from_decimal(myint) 16.3 us 11.4 us: 1.43x faster 10.8 us: 1.51x faster
Fraction.from_decimal(Decimal('1')) 10.9 us 7.94 us: 1.37x faster 7.88 us: 1.38x faster
Fraction.from_float(1) 7.06 us 4.25 us: 1.66x faster 7.68 us: 1.09x slower
Fraction.from_float(myint) 8.87 us 9.92 us: 1.12x slower 9.36 us: 1.06x slower
Fraction.from_float(1.1) 7.37 us 4.68 us: 1.57x faster 4.68 us: 1.58x faster
Geometric mean (ref) 1.49x faster 1.27x faster

Here patch2 is the current version. v1 had a hack to check isinstance(op, (int, Integral)) instead.

# bench.py
import pyperf
from fractions import Fraction as F
from decimal import Decimal as D
from numbers import Integral

runner = pyperf.Runner()
s = 'Fraction.from_decimal'
f = F.from_decimal
class myint:
    numerator = 123
    denominator = 1
    def __int__(self):
        return 123
    def __repr__(self):
        return "myint"
Integral.register(myint)
for v in [1, myint(), D(1)]:
    r = s + '(' + repr(v) + ')'
    runner.bench_func(r, f, v)
s = 'Fraction.from_float'
f = F.from_float
for v in [1, myint(), 1.1]:
    r = s + '(' + repr(v) + ')'
    runner.bench_func(r, f, v)
diff --git a/Lib/fractions.py b/Lib/fractions.py
index fa722589fb..d7887af9f8 100644
--- a/Lib/fractions.py
+++ b/Lib/fractions.py
@@ -335,23 +335,23 @@ def from_float(cls, f):
         Beware that Fraction.from_float(0.3) != Fraction(3, 10).
 
         """
-        if isinstance(f, numbers.Integral):
+        if not isinstance(f, float):
+            if not isinstance(f, (int, numbers.Integral)):
+                raise TypeError("%s.from_float() only takes floats, not %r (%s)" %
+                                (cls.__name__, f, type(f).__name__))
             return cls(f)
-        elif not isinstance(f, float):
-            raise TypeError("%s.from_float() only takes floats, not %r (%s)" %
-                            (cls.__name__, f, type(f).__name__))
         return cls._from_coprime_ints(*f.as_integer_ratio())
 
     @classmethod
     def from_decimal(cls, dec):
         """Converts a finite Decimal instance to a rational number, exactly."""
         from decimal import Decimal
-        if isinstance(dec, numbers.Integral):
-            dec = Decimal(int(dec))
-        elif not isinstance(dec, Decimal):
-            raise TypeError(
-                "%s.from_decimal() only takes Decimals, not %r (%s)" %
-                (cls.__name__, dec, type(dec).__name__))
+        if not isinstance(dec, Decimal):
+            if not isinstance(dec, (int, numbers.Integral)):
+                raise TypeError(
+                    "%s.from_decimal() only takes Decimals, not %r (%s)" %
+                    (cls.__name__, dec, type(dec).__name__))
+            dec = int(dec)
         return cls._from_coprime_ints(*dec.as_integer_ratio())
 
     @classmethod

@skirpichev skirpichev changed the title gh-72902: speedup Fraction.from_Decimal/float in typical cases gh-72902: speedup Fraction.from_decimal/float in typical cases May 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant