Skip to content

Commit

Permalink
Release 0.11.13 (#29)
Browse files Browse the repository at this point in the history
* chore: bump version refs to `0.11.13`

* refactor: refactor `ContinuedFraction.from_elements` to initialise new object from given elements without calling `__init__`

* docs: improvements to the docs for "negative" continued fractions + README + contributing docs
  • Loading branch information
sr-murthy authored Mar 15, 2024
1 parent 460fc09 commit cc07d48
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 10 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@

A simple extension of the Python [`fractions`](https://docs.python.org/3/library/fractions.html) standard library for working with [continued fractions](https://en.wikipedia.org/wiki/Continued_fraction) as Python objects.

The [PyPI package](https://pypi.org/project/continuedfractions/) only uses standard libraries and can be installed on any Python 3.10, 3.11, or 3.12 installation on Linux, Mac OS or Windows.
```shell
pip install continuedfractions
```

See the [project docs](https://continuedfractions.readthedocs.io/en/latest) for more details.

[Continued fractions](https://en.wikipedia.org/wiki/Continued_fraction) are beautiful and interesting mathematical objects, with many connections in [number theory](https://en.wikipedia.org/wiki/Number_theory) and also very useful practical applications, including the [rational approximation of real numbers](https://en.wikipedia.org/wiki/Continued_fraction#Best_rational_approximations).

The `continuedfractions` package is designed to:
Expand All @@ -29,4 +36,4 @@ The `continuedfractions` package is designed to:

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

See the [project docs](https://continuedfractions.readthedocs.io/en/latest) for more details.

Binary file added docs/_static/logo-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/sources/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ The CI/CD pipelines are defined in the `CI YML <.github/workflows/ci.yml>`_, and
Versioning and Releases
=======================

The package is currently at version ``0.11.12`` - `semantic versioning <https://semver.org/>`_ is used.
The `PyPI package <https://pypi.org/project/continuedfractions/>`_ is currently at version ``0.11.13`` - `semantic versioning <https://semver.org/>`_ is used.

There is currently no dedicated pipeline for releases - both `GitHub releases <https://github.com/sr-murthy/continuedfractions/releases>`_ and `PyPI packages <https://pypi.org/project/continuedfractions>`_ are published manually, but both have the same version tag.

Expand Down
71 changes: 66 additions & 5 deletions docs/sources/creating-continued-fractions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,68 @@ Compare this with :math:`[4; 2, 6, 7]`, which is the continued fraction represen
\frac{415}{93} = 4 + \cfrac{1}{2 + \cfrac{1}{6 + \cfrac{1}{7}}}
:py:class:`~continuedfractions.continuedfraction.ContinuedFraction` objects for negative numbers are constructed in the same way as with positive numbers, subject to the validation rules described above. And to avoid zero division problems if a fraction has a negative denominator the minus sign is “transferred” to the numerator. A few examples are given below.
To understand the difference in the sequence of elements more generally, we can start with `Euclid's division lemma <https://en.wikipedia.org/wiki/Euclidean_division#Division_theorem>`_ that for a positive rational number :math:`\frac{a}{b} > 1`, with :math:`a, b` coprime (no common divisors), there are positive integers :math:`q, v`, with :math:`0 < v < b`, such that :math:`a = qb + v`, so that:

.. math::
\begin{align}
\frac{a}{b} &= q + \frac{v}{b} \\
&= q + \frac{1}{\frac{b}{v}} \\
&= q + \frac{1}{R_1} \\
&= [a_0 = q; a_1, \ldots, a_n]
\end{align}
where :math:`R_1 = [a_1; a_2, \ldots, a_n] = \frac{b}{v}` is an :math:`(n - 1)`-order continued fraction which is the 1st :ref:`remainder <exploring-continued-fractions.remainders>` of the finite, simple continued fraction representation :math:`[a_0;a_1,\ldots,a_n]` of :math:`\frac{a}{b}`. Note that

.. math::
-a = -qb - v = -qb - b + b - v = -(q + 1)b + (b - v)
so we can write:

.. math::
\begin{align}
-\frac{a}{b} &= -(q + 1) + \frac{b - v}{b} \\
&= -(q + 1) + \frac{1}{\frac{b}{b - v}} \\
&= -(q + 1) + \frac{1}{1 + \frac{v}{b - v}} \\
&= -(q + 1) + \frac{1}{1 + \frac{1}{\frac{b}{v} - 1}} \\
&= -(q + 1) + \frac{1}{1 + \frac{1}{R_1 - 1}} \\
&= [a_0 = -(q + 1); 1, a_1 - 1,a_2,a_3,\ldots,a_n]
\end{align}
where :math:`R_1 - 1 = [a_1 - 1;a_2,\ldots,a_n]` and :math:`\frac{1}{R_1 - 1} = [0; a_1 - 1, a_2, a_3, \ldots,a_n]`. There are two cases: (1) :math:`a_1 = 1` in which case :math:`R_1` (for :math:`-\frac{a}{b}`) is the :math:`(n - 2)`-order continued fraction :math:`[1; a_2,\ldots, a_n] = [a_2 + 1; a_3,\ldots,a_n]`, or case (2) where :math:`R_1` is unchanged.

Thus, we can say that if :math:`[a_0;a_1,\ldots,a_n]` is the :math:`n`-order simple continued fraction representation of a positive rational number :math:`\frac{a}{b} > 1` then :math:`-\frac{a}{b}` has the following :math:`(n - 1)` and :math:`(n + 1)` simple continued representations for the cases :math:`a_1 = 1` and :math:`a_1 > 1`, respectively:

.. math::
-\frac{a}{b} =
\begin{cases}
[-(a_0 + 1); a_2 + 1,a_3,\ldots,a_n], & a_1 = 1 \\
[-(a_0 + 1); 1, a_1 - 1,a_2,a_3,\ldots,a_n], & a_1 > 1
\end{cases}
As :math:`n \to \infty` then :math:`\lim_{n \to \infty} [a_0;a_1,\ldots,a_n] = [a_0;a_1,\ldots]` represents an irrational number, and the same relations hold.

We can see this in action with :py:class:`~continuedfractions.continuedfraction.ContinuedFraction` objects:

.. code:: python
>>> ContinuedFraction(382, 225).elements
(1, 1, 2, 3, 4, 5)
>>> ContinuedFraction(-382, 225).elements
(-2, 3, 3, 4, 5)
>>> ContinuedFraction.from_elements(-2, 3, 3, 4, 5)
ContinuedFraction(-382, 225)
>>> ContinuedFraction(225, 157).elements
(1, 2, 3, 4, 5)
>>> ContinuedFraction(-225, 157).elements
(-2, 1, 1, 3, 4, 5)
>>> ContinuedFraction.from_elements(-2, 1, 1, 3, 4, 5)
ContinuedFraction(-225, 157)
The construction of :py:class:`~continuedfractions.continuedfraction.ContinuedFraction` objects via the ``__new__() -> __init__()`` cycle works the same way for negative numbers as with positive numbers, subject to the validation rules described above. And to avoid zero division problems if a fraction has a negative denominator the minus sign is “transferred” to the numerator. A few examples are given below.

.. code:: python
Expand All @@ -288,10 +349,10 @@ Compare this with :math:`[4; 2, 6, 7]`, which is the continued fraction represen
(-5, 1, 1, 6, 7)
>>> ContinuedFraction(-415, 93).convergents
mappingproxy({0: Fraction(-5, 1), 1: Fraction(-4, 1), 2: Fraction(-9, 2), 3: Fraction(-58, 13), 4: Fraction(-415, 93)})
>>> ContinuedFraction(-415, 93).as_float()
-4.462365591397849
>>> ContinuedFraction(415, 93).as_float()
4.462365591397849
>>> ContinuedFraction(-415, 93).as_decimal()
Decimal('-4.462365591397849462365591397849462365591397849462365591397849462365591397849462365591397849462365591')
>>> ContinuedFraction(415, 93).as_decimal()
Decimal('4.462365591397849462365591397849462365591397849462365591397849462365591397849462365591397849462365591')
**Note** As negation of numbers is a unary operation, the minus sign in a “negative” :py:class:`~continuedfractions.continuedfraction.ContinuedFraction` object must be attached to the fraction, before enclosure in parentheses.

Expand Down
8 changes: 7 additions & 1 deletion src/continuedfractions/continuedfraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,13 @@ def from_elements(cls, *elements: int) -> Fraction:
>>> c2
ContinuedFraction(200, 649)
"""
return cls(fraction_from_elements(*elements))
# Create a new ``ContinuedFraction`` object from the given elements
# and initialise with elements only - no need to initialise via
# ``__init__``
obj = cls(fraction_from_elements(*elements))
obj._elements = elements

return obj

def __init__(self, *args: int | float | str | Fraction | Decimal, **kwargs: Any) -> None:
"""
Expand Down
2 changes: 1 addition & 1 deletion src/continuedfractions/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.11.12"
__version__ = "0.11.13"
14 changes: 13 additions & 1 deletion tests/units/test_continuedfraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,16 +418,28 @@ def test_ContinuedFraction__left_mediant__two_fractions__correct_mediant_returne

assert cf1.mediant(cf2, dir='left', k=k) == expected_left_mediant

def test_ContinuedFraction__operations(self):
def test_ContinuedFraction__rational_operations(self):
f1 = ContinuedFraction(649, 200)
f2 = ContinuedFraction(-649, 200)

assert f1 + f2 == ContinuedFraction(0, 1)

assert f1 + 1 == 1 + f1 == ContinuedFraction('4.245')

assert f1.__radd__(f2) == f2.__radd__(f1)

assert f1.__radd__(1) == 1 + f1

assert f1 - f2 == ContinuedFraction(649, 100)

assert f1 - 1 == ContinuedFraction('2.245')

assert 1 - f1 == f1.__rsub__(1) == ContinuedFraction('-2.245')

assert f1 - 4 == ContinuedFraction('-0.755')

assert 4 - f1 == ContinuedFraction('0.755')

assert f1.__rsub__(f2) == f2.__radd__(-f1)

assert -f1 + f2 == ContinuedFraction(-649, 100)
Expand Down

0 comments on commit cc07d48

Please sign in to comment.