Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
* develop:
  Fix in classifier of Development status
  Fix rendering of long description on PyPi
  Fix missing setuptools import in setup.py, add manifest file
  Add setup.py file
  Add instrallation instructions, BSD license and version number
  Add changelog and readme with example usage and lib description
  Fixes in docstrings, add file-level docstrings, add example usage tests
  Add functions to comput convergents of a given value
  Add .editorconfig used for this project
  Add PyCharm inspection warnings
  Implement computation of a continued fraction
  • Loading branch information
TheMatjaz committed Apr 13, 2019
2 parents addda74 + 4cf66e5 commit 194eac8
Show file tree
Hide file tree
Showing 9 changed files with 524 additions and 9 deletions.
11 changes: 11 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[*]
charset=utf-8
end_of_line=lf
trim_trailing_whitespace=true
insert_final_newline=true
indent_style=space
indent_size=4

[*.md]
# A double trailing whitespace in Markdown indicates a newline `<br/>`.
trim_trailing_whitespace=false
25 changes: 25 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Changelog
===============================================================================

All notable changes to this project will be documented in this file.

The format is based on
[Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).


[1.0.0] - 2019-04-13
----------------------------------------

Initial version.


### Added

- Continued fractions of `int`, `float`,
`fractions.Fraction` and rational numbers expressed as tuples of 2 integers
`(numerator, denominator)`
- Convergents of the same data types
- Evaluate finite continued fraction into a value
- Arithmetical expression as string of a continued fraction
28 changes: 28 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
BSD 3-Clause License
===============================================================================

Copyright © 2019, Matjaž Guštin <dev@matjaz.it> <https://matjaz.it>.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of nor the names of its contributors may be used to endorse
or promote products derived from this software without specific prior
written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS “AS IS” AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include *.md
include test.py
74 changes: 74 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
ContFrac
===============================================================================

Continued fractions are a representation of numbers expressed as recursive
sums of integer parts and reciprocals of other numbers. _ContFrac_ is a
pure-Python3 lightweight module to compute and evaluate continued fractions,
as well as using them to approximate any number.


Features
----------------------------------------

- Supports conversion into continued fractions of `int`, `float`,
`fractions.Fraction` and rational numbers expressed as tuples of 2 integers
`(numerator, denominator)`, generated iteratively.
- Computes the convergents of the same data types, generated iteratively.
- Computes the value of a finite continued fraction.
- Generates the arithmetical expression as string of a continued fraction.


Installation
----------------------------------------

```bash
pip install contfrac
```

or just include the `contfrac.py` file in your project (copy-paste).


Example usage
----------------------------------------

```python
>>> import contfrac
>>> value = 415/93 # Express as (415, 93) to avoid rounding continued frac.
>>> coefficients = list(contfrac.continued_fraction(value))
>>> print(coefficients)
[4, 2, 6, 7]

>>> expression = contfrac.arithmetical_expr(coefficients)
>>> print('Value: {:f} = {:s}'.format(value, expression))
Value: 4.462366 = 4 + 1/(2 + 1/(6 + 1/(7)))

>>> # The evaluation of a float value from a continued fraction is subject
>>> # to floating point rounding errors
>>> eval_value = contfrac.evaluate(coefficients)
>>> print(eval_value, value) # Visible rounding errors
4.46236559139785 4.462365591397849

>>> convergents = list(contfrac.convergents(value))
>>> print(convergents)
[(4, 1), (9, 2), (58, 13), (415, 93)]

>>> import math
>>> coefficients = list(contfrac.continued_fraction(math.e, maxlen=10))
>>> print(coefficients)
[2, 1, 2, 1, 1, 4, 1, 1, 6, 1]

>>> convergent = contfrac.convergent(math.e, 3) # Low convergent grade
>>> print(convergent, convergent[0]/convergent[1], math.e)
(11, 4) 2.75 2.718281828459045

>>> convergent = contfrac.convergent(math.e, 7) # Higher grade = more accurate
>>> print(convergent, convergent[0]/convergent[1], math.e)
(193, 71) 2.7183098591549295 2.718281828459045
```


Similar libraries
----------------------------------------

- [Continued](https://github.com/MostAwesomeDude/continued), also available
through `pip`
161 changes: 158 additions & 3 deletions contfrac.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,110 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""Continued fractions and convergents library.
Continued fractions are a representation of numbers expressed as recursive
sums of integer parts and reciprocals of other numbers.
"""

import fractions
import typing

__VERSION__ = '1.0.0'


def continued_fraction(x, maxlen=30):
"""Computes the continued fraction of a number ``x`` expressed in many
types, including floats, tuples and Fractions, generating the coefficients
iteratively.
The result for a number ``x`` will be the sequence of coefficients
``[a, b, c, d, ...]```:
1
x = a + ---------------
1
b + -----------
1
c + -------
d + ...
Warning:
If ``x`` is a float, then rounding errors may occur and the continued
fraction is not guaranteed to look exactly as computed by hand,
although if its value is evaluated with ``evaluate()``, it may be
close-enough to ``x``.
To avoid such issues, consider providing ``x`` as a ratio of integers,
either as a tuple (numerator, denominator), which can be obtained from
a float with its method ``as_integer_ratio()``, or as Fraction.
Be aware that in this case the continued fraction may look
different than computed by hand but its value once evaluated will
be exactly the same as the input ratio.
Given the finite precision a floating point value has, even an
irrational number such as pi or e will be correctly represented
up to a certain point in the continued fraction. For example the golden
ratio will contain a ``2`` at index 38 of the continued fraction,
when it should be ``1``.
Args:
x (Union[Tuple[int,int], float, int, fractions.Fraction]): the value
to compute the continued fraction of.
maxlen (int): upper limit to the amount of the produced value,
especially useful when computing continued fractions of irrational
numbers.
Returns:
Generator[int, None, None]: continued fraction generated dynamically
"""
if maxlen <= 0:
raise ValueError('maxlen must be positive.')
elif isinstance(x, int):
return __int_cont_frac(x, 1, max_amount=1)
elif isinstance(x, float):
return __float_cont_frac(x, maxlen)
elif isinstance(x, fractions.Fraction):
return __int_cont_frac(x.numerator, x.denominator, maxlen)
elif isinstance(x, (tuple, list)):
return __int_cont_frac(x[0], x[1], maxlen)
else:
raise TypeError('Unsupported input type {:}'.format(type(x)))


def __int_cont_frac(num, den, max_amount):
amount = 0
while den != 0 and amount < max_amount:
integer_part = num // den
num -= integer_part * den
num, den = den, num
amount += 1
yield integer_part


def __float_cont_frac(real_number, max_amount):
fractional_part = 42
amount = 0
abs_tol = 10**-10
while not abs(fractional_part) <= abs_tol and amount < max_amount:
integer_part = int(round(real_number, 10))
fractional_part = real_number - integer_part
real_number = 1.0 / fractional_part
amount += 1
yield integer_part

def value_finite(cont_frac):

def evaluate(cont_frac):
"""Computes the floating point value of a finite continued fraction
representation.
That is the value of `c[0] + 1/(c[1] + 1/(c[2] + 1/(c[3] + ...)))`
for an input `c`.
That is the value of ``c[0] + 1/(c[1] + 1/(c[2] + 1/(c[3] + ...)))``
for an input ``c``.
Example:
For an input of ``[2,3,4]`` is ``2 + 1/(3 + 1/4) = 30/13`` expressed as
2.3076923076923075.
Args:
cont_frac (Iterable[Union[int, float]]): representation of a continued
Expand All @@ -35,6 +130,9 @@ def arithmetical_expr(cont_frac, with_spaces=True, force_floats=False):
by other programming languages or calculators. Beware of integer division
instead of true division in said language: use the `force_floats` argument.
Example:
It looks like ``2 + 1/(3 + 1/(4 + 1/(5)))`` for an input ``[2,3,4,5]``.
Args:
cont_frac (Iterable[Union[int, float]]): representation of a continued
fraction as iterable of numbers.
Expand All @@ -61,3 +159,60 @@ def arithmetical_expr(cont_frac, with_spaces=True, force_floats=False):
else:
joiner += '1/('
return joiner.join(parts) + ')' * i


def convergents(x, max_grade=10):
"""Computes the sequence of rational approximations of ``x`` up to the
given grade.
Warning:
The same warning as for ``continued_fraction()`` applies regarding
floating point rounding errors.
Args:
x (Union[Tuple[int,int], float, int, fractions.Fraction]): the value
to compute the convergents of.
max_grade (int): upper limit to the grade of the produced convergents.
The first convergent has grade 0 so the amount of yielded values
is ``max_grade + 1``. A higher grade convergent approximates
better the ``x`` value.
Returns:
Generator[Tuple[int, int], None, None]: sequence of (numerator,
denominator) rational numbers approximating ``x``.
"""
numerator_2_ago = 0
numerator_1_ago = 1
denominator_2_ago = 1
denominator_1_ago = 0
for coefficient in continued_fraction(x, maxlen=max_grade + 1):
numerator = coefficient * numerator_1_ago + numerator_2_ago
numerator_2_ago = numerator_1_ago
numerator_1_ago = numerator
denominator = coefficient * denominator_1_ago + denominator_2_ago
denominator_2_ago = denominator_1_ago
denominator_1_ago = denominator
yield numerator, denominator


def convergent(x, grade):
"""Computes the rational approximation of ``x`` of the provided grade.
Warning:
The same warning as for ``continued_fraction()`` applies regarding
floating point rounding errors.
Args:
x (Union[Tuple[int,int], float, int, fractions.Fraction]): the value
to compute the convergents of.
grade (int): the grade of the produced convergent. A higher grade
convergent approximates better the ``x`` value.
Returns:
Tuple[int, int]: pair (numerator, denominator) as rational number
approximating ``x``.
"""
element = None
for element in convergents(x, max_grade=grade):
pass
return element
Loading

0 comments on commit 194eac8

Please sign in to comment.