Decimal to fraction
August 13, 2023 at 6:17 PM by Dr. Drang
A few days ago, Rhett Allain, professor of physics education at Southeastern Louisana University and Wired columnist, posted a video in which he wrote a short Python script that used mediants to convert real numbers into fractions. I thought it was worth a few comments.
Here’s the video:
As you can see, Prof. Allain’s use of the mediant was inspired by a Matt Parker video from a few years ago. Basically, the mediant of two fractions, and is
One of the nice features of the mediant is that it lies between and . Prof. Allain uses this property to estimate the decimal portion of a real number as a fraction and iteratively improve that estimate.
The number he uses is 3.213432 and the estimate his code prints out is
x = 3 + 1.50279e+6/7.04108e+6
There are two odd things odd about this. First, the numerator and denominator are given in exponential form and because they’re rounded to six digits, we don’t actually see the final digit of either one. This is due to a combination of Prof. Allain using Web VPython and not using f-strings in his print
statement. If he’d used f-strings, e.g.,
print(f'x = {xint:0f} + {n2}/{d2}')
his output would have been
x = 3 + 1502792/7041081
from which we can see the final digits of the numerator and denominator.
But that’s just a quirk of the Python implementation he’s using. The more fundamental oddity is the second one: his denominator is much larger than it should be. After all, since there are only six decimal places in 3.213432, we can write it immediately as
and can reduce it to
There’s clearly no reason to have a denominator of over 7 million. The reason you want use a fraction to estimate a real number is to get something that’s simpler, not something that’s more complicated.
The way to get the best fractional representation of a real number is to use continued fractions and their convergents. I launched Mathematica and asked it to give me the continued fraction representation of 0.213432:
In[1]:= ContinuedFraction[3.213432]
Out[1]= {3, 4, 1, 2, 5, 1, 1, 1, 1, 1, 2, 23}
What this means is
and so on for all the terms.1
The convergents are truncations of the continued fraction. To get the fractional estimates of the decimal portion of 3.213432, I ran
In[2]:= Convergents[ContinuedFraction[0.213432]]
1 1 3 16 19 35 54 89 143 375 8768
Out[2]= {0, -, -, --, --, --, ---, ---, ---, ---, ----, -----}
4 5 14 75 89 164 253 417 670 1757 41081
Since
we’ve gotten very close to 0.213432 with just a 5-digit denominator. The previous convergent is also pretty good with a smaller denominator:
For my money, this is the fraction to use. While iterating with the mediant may be a fun programming exercise, continued fractions will get you a nicer looking fractional estimate.
Speaking of money, if you don’t own Mathematica but would like to play around with its continued fraction functions, you can install the Wolfram Engine for free and run code interactively in a terminal via the wolframscript
command. You can’t get graphics or nicely formatted equations in wolframscript
, but sometimes text is good enough. In fact, while I did the calculations for this post in Mathematica first, I ran them again in wolframscript
to get the text output you see above.
One last thing about continued fractions: they’re the easiest way to see how some common approximations of were arrived at.
In[3]:= ContinuedFraction[Pi, 5]
Out[3]= {3, 7, 15, 1, 292}
The second argument to ContinuedFraction
tells it to stop at 5 terms. The sudden jump from 1 to 292 tells us that the fifth and subsequent terms add relatively little to the estimate. Now we can look at the convergents:
In[4]:= Convergents[ContinuedFraction[Pi, 5]]
22 333 355 103993
Out[4]= {3, --, ---, ---, ------}
7 106 113 33102
Although is a pretty decent approximation of , is much better (accurate to 7 significant digits instead of 3) and is still fairly short. The fifth convergent gives us only 2 more significant digits at the expense of many more digits in the numerator and denominator.
-
By default, Mathematica truncates the continued fraction of a decimal number when the fraction is in some sense “close enough” to the argument. The documentation says it “generates a list of all terms that can be obtained given the precision of” the input. ↩