← 返回首页
gh-93476: Fraction.limit_denominator speed increase by mscuthbert · Pull Request #93477 · python/cpython · GitHub
Skip to content

Navigation Menu

Toggle navigation
Sign in
Appearance settings
Search or jump to...

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Include my email address so I can be contacted

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Resetting focus
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .py  (1) .rst  (1) All 2 file types selected Viewed files
Conversations
Failed to load comments. Retry
Loading
Jump to
Jump to file
Failed to load files. Retry
Loading
Diff view
Unified
Split
Hide whitespace
Apply and reload
Show whitespace
Diff view
Unified
Split
Hide whitespace
Apply and reload
22 changes: 17 additions & 5 deletions Lib/fractions.py
Show comments View file Edit file Delete file Open in desktop
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ def limit_denominator(self, max_denominator=1000000):

p0, q0, p1, q1 = 0, 1, 1, 0
n, d = self._numerator, self._denominator
n_original, d_original = n, d
while True:
a = n//d
q2 = q0+a*q1
Expand All @@ -247,12 +248,23 @@ def limit_denominator(self, max_denominator=1000000):
n, d = d, n-a*d

k = (max_denominator-q0)//q1
bound1 = Fraction(p0+k*p1, q0+k*q1)
bound2 = Fraction(p1, q1)
if abs(bound2 - self) <= abs(bound1-self):
return bound2
bound1_n = p0 + k*p1
bound1_d = q0 + k*q1
bound2_n = p1
bound2_d = q1
Comment thread
mscuthbert marked this conversation as resolved.
Show resolved Hide resolved

# diff1_n = numerator of (bound1 minus self) as a Fraction;
# etc. for diff1_d, diff2_n, diff2_d
diff1_n = abs(bound1_n*d_original - n_original*bound1_d)
diff1_d = d_original * bound1_d
diff2_n = abs(bound2_n*d_original - n_original*bound2_d)
diff2_d = d_original * bound2_d

if diff1_n * diff2_d >= diff2_n * diff1_d:
# bound2 is closer (or equal) to original as bound 1
return Fraction(bound2_n, bound2_d, _normalize=False)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality Hide comment

I just realised that I'm not 100% convinced that the result here is always normalised. There are two parts to being normalised: the numerator must be relatively prime to the denominator, and the denominator must be positive. I'd thought about the first part (which is fine), but not about the second.

Is it clear that bound2_d and bound1_d are positive at this point?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality Hide comment

Answer: yes, it's true that bound2_d and bound1_d are positive at this point, though perhaps not 100% clear. In the Euclidean loop above, on the very first iteration a can be negative, but on all subsequent iterations a is nonnegative. And on the first iteration q1 = 0, so that potentially negative a does no harm, and q0 and q1 are guaranteed nonnegative at all times. Then it's easy to check that they must in fact be positive at the point where the loop exits.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality Hide comment

That was my reading of the loop as well, but I didn't write the code (nor name the variables) so I didn't want to change this without another set of eyes; thanks!

else:
return bound1
return Fraction(bound1_n, bound1_d, _normalize=False)

@property
def numerator(a):
Expand Down
Show comments View file Edit file Delete file Open in desktop
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:meth:`fractions.Fraction.limit_denominator()` performance enhancements.
Patch by Michael Scott Asato Cuthbert.
Toggle all file notes Toggle all file annotations

Footer

© 2026 GitHub, Inc.