Skip to content

Commit fb4be78

Browse files
authored
Merge pull request #8 from sr-murthy/release-0.0.6
Release 0.0.6
2 parents 1b37aad + 415efd9 commit fb4be78

File tree

6 files changed

+217
-55
lines changed

6 files changed

+217
-55
lines changed

README.md

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ A simple extension of the Python [`fractions`](https://docs.python.org/3/library
1919
## Prelude
2020

2121
$$
22-
\pi = 3 + \frac{1}{7 + \frac{1}{15 + \frac{1}{1 + \frac{1}{292 + \cdots}}}}
22+
\pi = 3 + \frac{1}{7 + \frac{1}{15 + \frac{1}{1 + \frac{1}{292 + \ddots}}}}
2323
$$
2424

2525
```python
@@ -43,7 +43,7 @@ ContinuedFraction(355, 113)
4343
>>> math.pi - pi_approx.as_float()
4444
-2.667641894049666e-07
4545
>>> import pytest
46-
>>> pytest.approx(pi_approx.as_float(), rel=decimal.getcontext().prec) == math.pi
46+
>>> pytest.approx(pi_approx.as_float(), rel=1e-12) == math.pi
4747
True
4848
>>> ContinuedFraction('3.245')
4949
ContinuedFraction(649, 200)
@@ -85,7 +85,7 @@ The functions in `continuedfractions.lib` are standalone and thus useful on thei
8585

8686
### A Simple Introduction with Examples
8787

88-
From a user perspective it is easiest to use the [`continuedfractions.continuedfraction.ContinuedFraction`](https://github.com/sr-murthy/continuedfractions/blob/main/src/continuedfraction.py) class. A simple introduction is given below with a variety of examples.
88+
The starting point is the [`continuedfractions.continuedfraction.ContinuedFraction`](https://github.com/sr-murthy/continuedfractions/blob/main/src/continuedfraction.py) class. A simple introduction is given below of it can be used, with a variety of examples.
8989

9090
#### Importing the `ContinuedFraction` Class
9191

@@ -221,32 +221,74 @@ A related concept is that of **remainders** of continued fractions, which are (p
221221
(ContinuedFraction(649, 200), ContinuedFraction(200, 49), ContinuedFraction(49, 4), ContinuedFraction(4, 1))
222222
```
223223

224-
Another feature which the package includes is [mediants](https://en.wikipedia.org/wiki/Mediant_(mathematics)). The mediant of two rational numbers $\frac{a}{b}$ and $\frac{c}{d}$, where $b, d \neq 0$, is given by the fraction:
224+
#### Mediants
225+
226+
Another feature which the package includes is [mediants](https://en.wikipedia.org/wiki/Mediant_(mathematics)). The (simple) mediant of two rational numbers $\frac{a}{b}$ and $\frac{c}{d}$, where $b, d \neq 0$, is defined as the rational number:
225227

226228
$$
227229
\frac{a + c}{b + d}
228230
$$
229231

230-
and has the property that:
232+
Assuming that $\frac{a}{b} < \frac{c}{d}$ and $cd > 0$ the mediant above has the property that:
231233

232234
$$
233235
\frac{a}{b} < \frac{a + c}{b + d} < \frac{c}{d}
234236
$$
235237

236-
assuming $\frac{a}{b} < \frac{c}{d}$ and $cd > 0$.
237-
238-
The `ContinuedFraction` class provides a `.mediant()` method for objects to compute their mediants with a given fraction, which could be another `ContinuedFraction` or `fractions.Fraction` object. The result is also a `ContinuedFraction` object. A few examples are given below.
238+
Mediants can give good rational approximations to real numbers of interest.
239239

240+
The `ContinuedFraction` class provides a `.mediant()` method which can be used to calculate mediants with other `ContinuedFraction` or `fractions.Fraction` objects. The result is also a `ContinuedFraction` object. A few examples are given below of how to calculate mediants.
240241

241242
```python
242243
>>> ContinuedFraction('0.5').mediant(Fraction(2, 3))
243244
>>> ContinuedFraction(3, 5)
245+
>>> ContinuedFraction('0.6').elements
246+
(0, 1, 1, 2)
244247
>>> ContinuedFraction(1, 2).mediant(ContinuedFraction('2/3'))
245248
>>> ContinuedFraction(3, 5)
246249
>>> assert ContinuedFraction(1, 2) < ContinuedFraction(1, 2).mediant(Fraction(3, 4)) < ContinuedFraction(3, 4)
247250
# True
248251
````
249252

253+
The concept of the simple mediant of two fractions $\frac{a}{b}$ and $\frac{c}{d}$ as given above can be generalised to $k$-th mediants, which can be defined as follows:
254+
255+
$$
256+
\frac{a + kc}{b + kd}
257+
$$
258+
259+
Assuming that $\frac{a}{b} < \frac{c}{d}$ and $cd > 0$ we have, as $k$ increases, a strictly increasing sequence of fractions bounded by $\frac{a}{b}$ and $\frac{c}{d}$ on either side:
260+
261+
$$
262+
\frac{a}{b} < \frac{a + c}{b + d} < \frac{a + 2c}{b + 2d} < \frac{a + 3c}{b + 3d} < \cdots < \frac{c}{d}
263+
$$
264+
265+
Thus, the sequence of mediants as defined above converges to $\frac{c}{d}$ as $k$ increases.
266+
267+
$$
268+
\lim_{k \to \infty} \frac{a + kc}{b + kd} = \frac{c}{d}
269+
$$
270+
271+
We can illustrate this using `ContinuedFraction.mediant` using increasing values of the mediant order `k`, which defaults to `k=1`.
272+
273+
```python
274+
>>> c1 = ContinuedFraction(1, 2)
275+
>>> c2 = ContinuedFraction(3, 5)
276+
>>> c1.mediant(c2)
277+
ContinuedFraction(4, 7)
278+
>>> c1.mediant(c2).as_float()
279+
0.5714285714285714
280+
>>> c1.mediant(c2, k=10)
281+
0.5961538461538461
282+
>>> c1.mediant(c2, k=100)
283+
0.599601593625498
284+
>>> c1.mediant(c2, k=10 ** 6)
285+
ContinuedFraction(3000001, 5000002)
286+
>>> c1.mediant(c2, k=10 ** 6).as_float()
287+
0.599999960000016
288+
```
289+
290+
Mediants have other [interesting connections](https://en.wikipedia.org/wiki/Stern%E2%80%93Brocot_tree), including to sequences and orderings of rational numbers.
291+
250292
### Constructing Continued Fractions from Element Sequences
251293

252294
Continued fractions can also be constructed from element sequences, using the `ContinuedFraction.from_elements()` class method. Because `ContinuedFraction` is a subclass of `fractions.Fraction` all `ContinuedFraction` objects are fully operable as rational numbers, including as negative rationals.
@@ -424,25 +466,30 @@ The CI/CD pipelines are defined in the [CI YML](.github/workflows/ci.yml), and p
424466

425467
### Versioning & Package Publishing
426468

427-
The package is currently at version `0.0.4`, and packages are published manually to [PyPI](https://pypi.org/project/continuedfractions/). There is currently no release pipeline - this will be added later.
469+
The package is currently at version `0.0.6` ([semantic versioning](https://semver.org/) is used), and packages are published manually to [PyPI](https://pypi.org/project/continuedfractions/) using [twine](https://twine.readthedocs.io/en/stable/). There is currently no CI release pipeline - this will be added later.
428470

429471
## License
430472

431473
The project is [licensed](LICENSE) under the [Mozilla Public License 2.0](https://opensource.org/licenses/MPL-2.0).
432474

433-
434475
## References
435476

436-
[1] Barrow, John D. “Chaos in Numberland: The secret life of continued fractions.” plus.maths.org, 1 June 2000, https://plus.maths.org/content/chaos-numberland-secret-life-continued-fractionsURL.
477+
[1] Baker, Alan. A concise introduction to the theory of numbers. Cambridge: Cambridge Univ. Pr., 2002.
478+
479+
[2] Barrow, John D. “Chaos in Numberland: The secret life of continued fractions.” plus.maths.org, 1 June 2000, https://plus.maths.org/content/chaos-numberland-secret-life-continued-fractionsURL.
480+
481+
[3] Emory University Math Center. “Continued Fractions.” The Department of Mathematics and Computer Science, https://mathcenter.oxford.emory.edu/site/math125/continuedFractions/. Accessed 19 Feb 2024.
482+
483+
[4] Python 3.12.2 Docs. "Floating Point Arithmetic: Issues and Limitations." https://docs.python.org/3/tutorial/floatingpoint.html. Accessed 20 February 2024.
437484

438-
[2] Emory University Math Center. “Continued Fractions.” The Department of Mathematics and Computer Science, https://mathcenter.oxford.emory.edu/site/math125/continuedFractions/. Accessed 19 Feb 2024.
485+
[5] Python 3.12.2 Docs. "fractions - Rational numbers." https://docs.python.org/3/library/fractions.html. Accessed 21 February 2024.
439486

440-
[3] Python 3.12.2 Docs. "Floating Point Arithmetic: Issues and Limitations." https://docs.python.org/3/tutorial/floatingpoint.html. Accessed 20 February 2024.
487+
[6] Python 3.12.2 Docs. "decimal - Decimal fixed point and floating point arithmetic." https://docs.python.org/3/library/decimal.html. Accessed 21 February 2024.
441488

442-
[4] Wikipedia. "Continued Fraction". https://en.wikipedia.org/wiki/Continued_fraction. Accessed 19 February 2024.
489+
[7] Wikipedia. "Continued Fraction". https://en.wikipedia.org/wiki/Continued_fraction. Accessed 19 February 2024.
443490

444491
[^1]: Due to the nature of [binary floating point arithmetic](https://docs.python.org/3/tutorial/floatingpoint.html) it is not always possible to exactly represent a given [real number](https://en.wikipedia.org/wiki/Real_number). For the same reason, the continued fraction representations produced by the package will necessarily be [finite](https://en.wikipedia.org/wiki/Continued_fraction#Finite_continued_fractions).
445492

446493
[^2]: The definition of "rational number" used here is standard: a ratio $\frac{a}{b}$ of integers $a$ and $b \neq 0$. If $a$ and $b$ have no common denominator then \frac{a}{b} is irreducible and unique.
447494

448-
[^3]: This is due to the [theorem](https://en.wikipedia.org/wiki/Monotone_convergence_theorem) that every bounded monotonic sequence of real numbers converges.
495+
[^3]: This is due to the [theorem](https://en.wikipedia.org/wiki/Monotone_convergence_theorem) that every bounded monotonic sequence of real numbers converges.

src/continuedfractions/continuedfraction.py

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
continued_fraction_rational,
2626
continued_fraction_real,
2727
fraction_from_elements,
28-
kth_convergent,
28+
convergent,
29+
mediant,
2930
)
3031

3132

@@ -380,10 +381,10 @@ def __init__(self, *args: int | float | str | Fraction | Decimal, **kwargs: Any
380381
else: # pragma: no cover
381382
raise ValueError(self.__class__.__valid_inputs_msg__)
382383

383-
_kth_convergent = partial(kth_convergent, *self._elements)
384+
_convergent = partial(convergent, *self._elements)
384385
self._convergents = MappingProxyType(
385386
{
386-
k: _kth_convergent(k=k)
387+
k: _convergent(k=k)
387388
for k in range(len(self._elements))
388389
}
389390
)
@@ -591,39 +592,59 @@ def remainder(self, k: int, /) -> Fraction:
591592
"""
592593
return self.__class__.from_elements(*self._elements[k:])
593594

594-
def mediant(self, other: Fraction) -> Fraction:
595+
def mediant(self, other: Fraction, k: int = 1) -> Fraction:
595596
"""
596-
The continued fraction of the rational number formed by taking the
597-
pairwise sum of the numerators and denominators of the original
598-
continued fraction and a second fraction (`other`). The resulting
599-
fraction has the property that its value lies between its two
600-
constituents.
597+
Returns a `ContinuedFraction` object of the `fractions.Fraction`
598+
objects representing two rational numbers `r = a / b` and `s = c / d`,
599+
by taking their `k`-the mediant, for a positive integer `k`.
600+
601+
The `k`-th mediant of rationals `r = a / b` and `s = c / d`, where of
602+
course the denominators are assumed to be non-zero and `k` is a
603+
positive integer, is given by
604+
::
605+
606+
(a + kc) / (b + kd)
607+
608+
The 1st mediant is the fraction `(a + c) / (b + d)`.
609+
610+
Assuming that `a / b` < `c / d` and `cd > 0` their `k`-order mediants
611+
have the property that:
612+
::
613+
614+
a / b < (a + c) / (b + d) < (a + 2c) / (a + 2d) < ... c / d
615+
616+
As `k` goes to infinity the sequence of these mediants converges to
617+
`s = c / d`, by the bounded monotone convergence theorem for real numbers.
601618
602619
Parameters
603620
----------
604621
other : fractions.Fraction or ContinuedFraction
605-
The second fraction to use to calculate the mediant with the
606-
first.
622+
The second fraction to use to calculate the `k`-th mediant with
623+
the first.
624+
625+
k : int, default=1
626+
The order of the mediant, as defined above.
607627
608628
Returns
609629
-------
610630
ContinuedFraction
611-
The mediant of the original and the second fractions, as a
612-
`ContinuedFraction` instance.
631+
The `k`-th mediant of the original fraction and the second
632+
fraction, as a `ContinuedFraction` instance.
613633
614634
Examples
615635
--------
616-
>>> cf = ContinuedFraction('.12345')
617-
>>> cf
618-
ContinuedFraction(2469, 20000)
619-
>>> cf.mediant(Fraction('.1235'))
620-
ContinuedFraction(679, 5500)
636+
>>> c1 = ContinuedFraction('.5')
637+
>>> c2 = ContinuedFraction(3, 5)
638+
>>> c1, c2
639+
(ContinuedFraction(1, 2), ContinuedFraction(3, 5))
640+
>>> c1.mediant(c2)
641+
ContinuedFraction(4, 7)
642+
>>> c1.mediant(c2, k=2)
643+
ContinuedFraction(7, 12)
644+
>>> c1.mediant(c2, k=3)
645+
ContinuedFraction(10, 17)
621646
"""
622-
return self.__class__(
623-
self.numerator + other.numerator,
624-
self.denominator + other.denominator
625-
)
626-
647+
return self.__class__(mediant(self, other, k=k))
627648

628649

629650
if __name__ == "__main__": # pragma: no cover

src/continuedfractions/lib.py

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
'continued_fraction_real',
33
'continued_fraction_rational',
44
'fraction_from_elements',
5-
'kth_convergent',
5+
'convergent',
6+
'mediant',
67
]
78

89

@@ -253,7 +254,7 @@ def fraction_from_elements(*elements: int) -> Fraction:
253254
return elements[0] + Fraction(1, fraction_from_elements(*elements[1:]))
254255

255256

256-
def kth_convergent(*elements: int, k: int = 1) -> Fraction:
257+
def convergent(*elements: int, k: int = 1) -> Fraction:
257258
"""
258259
Returns a `fractions.Fraction` object representing the `k`-th convergent of
259260
a (finite) continued fraction given by an ordered sequence of its (integer)
@@ -292,24 +293,24 @@ def kth_convergent(*elements: int, k: int = 1) -> Fraction:
292293
293294
Examples
294295
--------
295-
>>> kth_convergent(3, 4, 12, 4, k=0)
296+
>>> convergent(3, 4, 12, 4, k=0)
296297
Fraction(3, 1)
297298
298-
>>> kth_convergent(3, 4, 12, 4, k=1)
299+
>>> convergent(3, 4, 12, 4, k=1)
299300
Fraction(13, 4)
300301
301-
>>> kth_convergent(3, 4, 12, 4, k=2)
302+
>>> convergent(3, 4, 12, 4, k=2)
302303
Fraction(159, 49)
303304
304-
>>> kth_convergent(3, 4, 12, 4, k=3)
305+
>>> convergent(3, 4, 12, 4, k=3)
305306
Fraction(649, 200)
306307
307-
>>> kth_convergent(3, 4, 12, 4, k=-1)
308+
>>> convergent(3, 4, 12, 4, k=-1)
308309
Traceback (most recent call last):
309310
...
310311
ValueError: `k` must be a non-negative integer less than the number of elements of the continued fraction
311312
312-
>>> kth_convergent(3, 4, 12, 4, k=4)
313+
>>> convergent(3, 4, 12, 4, k=4)
313314
Traceback (most recent call last):
314315
...
315316
ValueError: `k` must be a non-negative integer less than the number of elements of the continued fraction
@@ -323,6 +324,58 @@ def kth_convergent(*elements: int, k: int = 1) -> Fraction:
323324
return fraction_from_elements(*elements[:k + 1])
324325

325326

327+
def mediant(r: Fraction, s: Fraction, k: int = 1) -> Fraction:
328+
"""
329+
Returns the `k-th mediant of two rational numbers `r = a / b` and
330+
`s = c / d`, given as `fractions.Fraction` objects, where it is
331+
assumed that the denominators `b` and `d` are non-zero. The `k`-th mediant
332+
of `r` and `s` is defined as:
333+
::
334+
335+
(a + kc) / (b + kd)
336+
337+
The 1st mediant is given by `(a + c) / (b + d)`.
338+
339+
Assuming that `a / b` < `c / d` and `cd > 0` their `k`-order mediants have
340+
the property that:
341+
::
342+
343+
a / b < (a + c) / (b + d) < (a + 2c) / (a + 2d) < ... c / d
344+
345+
As `k` goes to infinity the sequence of these mediants converges to
346+
`s = c / d`, by the bounded monotone convergence theorem for real numbers.
347+
348+
Parameters
349+
----------
350+
r : fractions.Fraction
351+
The first rational number.
352+
353+
s : fractions.Fraction
354+
The second rational number.
355+
356+
k : int, default=1
357+
The order of the mediant, as defined above.
358+
359+
Returns
360+
-------
361+
fractions.Fraction
362+
The `k`-th mediant of the two given rational numbers.
363+
364+
Examples
365+
--------
366+
>>> mediant(Fraction(1, 2), Fraction(3, 5))
367+
Fraction(4, 7)
368+
>>> mediant(Fraction(1, 2), Fraction(3, 5), k=2)
369+
Fraction(7, 12)
370+
>>> mediant(Fraction(1, 2), Fraction(3, 5), k=3)
371+
Fraction(10, 17)
372+
"""
373+
a, b = r.as_integer_ratio()
374+
c, d = s.as_integer_ratio()
375+
376+
return Fraction(a + k * c, b + k * d)
377+
378+
326379
if __name__ == "__main__": # pragma: no cover
327380
# Doctest the module from the project root using
328381
#

src/continuedfractions/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.0.5"
1+
__version__ = "0.0.6"

tests/units/test_continuedfraction.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,25 @@ def test_ContinuedFraction__creation_and_initialisation__valid_inputs__object_co
333333

334334
assert received.mediant(1) == expected_ref_mediant
335335

336+
@pytest.mark.parametrize(
337+
"cf1, cf2, k, expected_mediant",
338+
[
339+
(ContinuedFraction(1, 2), Fraction(3, 5), 1, ContinuedFraction(4, 7)),
340+
(ContinuedFraction(1, 2), ContinuedFraction(3, 5), 2, ContinuedFraction(7, 12)),
341+
(ContinuedFraction(1, 2), Fraction(3, 5), 3, ContinuedFraction(10, 17)),
342+
(ContinuedFraction(1, 2), ContinuedFraction(0), 1, ContinuedFraction(1, 3)),
343+
(ContinuedFraction(1, 2), Fraction(1, 2), 1, ContinuedFraction(1, 2)),
344+
(ContinuedFraction(1, -2), ContinuedFraction(1, 2), 1, ContinuedFraction(0, 1)),
345+
(ContinuedFraction(-1, 2), Fraction(1), 1, ContinuedFraction(0, 1)),
346+
(ContinuedFraction(-1, 2), ContinuedFraction(-1), 1, ContinuedFraction(-2, 3)),
347+
(ContinuedFraction(-1, 2), Fraction(1, -2), 1, ContinuedFraction(-1, 2)),
348+
(ContinuedFraction(1, 2), Fraction(3, 5), 10 ** 6, ContinuedFraction(3000001, 5000002)),
349+
],
350+
)
351+
def test_mediant__two_ordered_fractions__correct_mediant_continued_fraction_returned(self, cf1, cf2, k, expected_mediant):
352+
353+
assert cf1.mediant(cf2, k=k) == expected_mediant
354+
336355
def test_ContinuedFraction__operations(self):
337356
f1 = ContinuedFraction(649, 200)
338357
f2 = ContinuedFraction(-649, 200)

0 commit comments

Comments
 (0)