Thanks to visit codestin.com
Credit goes to github.com

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
@skirpichev
Copy link
Member Author

This is a second part of improvements, cherry-picked from the issue thread.

I think this is less controversial, as speedup affects main use-cases (i.e. Decimal's or float's, respectively). With v2 version we haven't speed regressions for integer case. Though, I'm not sure about using that hack.

@neonene
Copy link
Contributor

neonene commented May 17, 2025

Please consider import decimal; isinstance(dec, decimal.Decimal) as well.

This is slightly slower:

sk@note:~ $ python3.13 -m timeit -s 'import decimal;o1=decimal.Decimal(1);o2=1j' \
    'isinstance(o1, decimal.Decimal)'
5000000 loops, best of 5: 82.1 nsec per loop
sk@note:~ $ python3.13 -m timeit -s 'import decimal;o1=decimal.Decimal(1);o2=1j' \
    'isinstance(o2, decimal.Decimal)'
2000000 loops, best of 5: 159 nsec per loop
sk@note:~ $ python3.13 -m timeit -s 'from decimal import Decimal;o1=Decimal(1);o2=1j' \
    'isinstance(o1, Decimal)'
5000000 loops, best of 5: 65.5 nsec per loop
sk@note:~ $ python3.13 -m timeit -s 'from decimal import Decimal;o1=Decimal(1);o2=1j' \
    'isinstance(o2, Decimal)'
2000000 loops, best of 5: 139 nsec per loop

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.

2 participants