Module lange.tricks

Expand source code
# Based on:
# https://stackoverflow.com/questions/3018758/determine-precision-and-ret-of-particular-number-in-python


#  Copyright (c) 2021. Davi Pereira dos Santos
#  This file is part of the lange project.
#  Please respect the license - more about this in the section (*) below.
#
#  lange is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  lange is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with lange.  If not, see <http://www.gnu.org/licenses/>.
#
#  (*) Removing authorship by any means, e.g. by distribution of derived
#  works or verbatim, obfuscated, compiled or rewritten versions of any
#  part of this work is a crime and is unethical regarding the effort and
#  time spent here.
#  Relevant employers or funding agencies will be notified accordingly.

import decimal as dec
from functools import partial
from math import nan


def detect_precision(x, maxdigits=28):
    """
    Detect the precision (include digits before and after the decimal separator).

    The checking only works if 'max_digits' is large enough.

    Usage:
        >>> detect_precision(3)
        1
        >>> detect_precision(3.7)
        2
        >>> detect_precision(3.0)
        1
        >>> detect_precision(30.0e-4)
        4
        >>> detect_precision(30.0e4)
        6

    Based on:
    https://stackoverflow.com/questions/3018758/determine-precision-and-ret-of-particular-number-in-python
    """
    if x is ...:
        return 0
    ctx = dec.Context()
    ctx.prec = maxdigits
    d1 = ctx.create_decimal(repr(float(x)))
    txt = format(d1, "f")
    l = len(txt) - 1
    if int(x) == x:
        l -= 1
    return l


def list2progression(lst, maxdigits=28):
    """Convert list representing A. or G. progression to lange

    >>> list2progression([0,1,2,...,9])
    [0 1 .+. 9]
    >>> list2progression([1,2,4,...,16])
    [1 2 .*. 16]
    >>> list2progression([1,2,4,...])
    [1 2 .*. ∞]
    >>> list2progression([1,-2,4,...])
    [1 -2 .*. ∞]
    >>> list2progression([1,-2,4,...,64]).l
    [1, -2, 4, -8, 16, -32]
    >>> list2progression([])
    []
    >>> list2progression([1])
    [1]
    >>> list2progression([1,2])
    [1 2]
    >>> list2progression([1,2,3])
    [1 2 3]
    >>> list2progression([1,2,3,4])
    [1 2 3 4]
    >>> list2progression([1,2,3,4,5])
    [1 2 3 4 5]
    >>> list2progression([1,2,3,4,5,6])
    [1 2 3 4 5 6]

    Parameters
    ----------
    lst

    Returns
    -------

    """
    if ... not in lst:  # pragma: no cover
        from lange.ap import AP

        return AP(*lst)  # Use AP as fallback for undefined progression.
    if len(lst) not in [4, 5] or lst[3] is not ...:  # pragma: no cover
        raise Exception(
            f"Cannot guess if you want an arithmetic or a geometric progression. Provide 3 numbers followed by '...', not {lst}."
        )

    # Protect diffs and ratios from floating point inequality issues (e.g. 0.8 - 0.6 != 0.2).
    precision = max(map(partial(detect_precision, maxdigits=maxdigits), lst))
    decctx = dec.Context()
    decctx.prec = precision
    lst_dec = [decctx.create_decimal(x) for x in lst if x is not ...]

    # Calculate diffs and ratios.
    try:
        diff1 = lst_dec[1] - lst_dec[0]
        diff2 = lst_dec[2] - lst_dec[1]
        ratio1 = nan if lst_dec[0] == 0 else lst_dec[1] / lst_dec[0]
        ratio2 = nan if lst_dec[1] == 0 else lst_dec[2] / lst_dec[1]
    except:  # pragma: no cover
        raise InconsistentLange(f"Cannot identify whether this is a G. or A. progression: {lst}")
    newlst = lst[0:2] + lst[3:]

    if diff1 == diff2:
        from lange.ap import AP

        return AP(*newlst)
    elif ratio1 == ratio2:
        from lange.gp import GP

        return GP(*newlst)
    else:  # pragma: no cover
        raise InconsistentLange(
            f"Cannot identify whether this is a G. or A. Progression: {lst}", diff1, diff2, ratio1, ratio2
        )


class InconsistentLange(Exception):
    pass

Functions

def detect_precision(x, maxdigits=28)

Detect the precision (include digits before and after the decimal separator).

The checking only works if 'max_digits' is large enough.

Usage

>>> detect_precision(3)
1
>>> detect_precision(3.7)
2
>>> detect_precision(3.0)
1
>>> detect_precision(30.0e-4)
4
>>> detect_precision(30.0e4)
6

Based on: https://stackoverflow.com/questions/3018758/determine-precision-and-ret-of-particular-number-in-python

Expand source code
def detect_precision(x, maxdigits=28):
    """
    Detect the precision (include digits before and after the decimal separator).

    The checking only works if 'max_digits' is large enough.

    Usage:
        >>> detect_precision(3)
        1
        >>> detect_precision(3.7)
        2
        >>> detect_precision(3.0)
        1
        >>> detect_precision(30.0e-4)
        4
        >>> detect_precision(30.0e4)
        6

    Based on:
    https://stackoverflow.com/questions/3018758/determine-precision-and-ret-of-particular-number-in-python
    """
    if x is ...:
        return 0
    ctx = dec.Context()
    ctx.prec = maxdigits
    d1 = ctx.create_decimal(repr(float(x)))
    txt = format(d1, "f")
    l = len(txt) - 1
    if int(x) == x:
        l -= 1
    return l
def list2progression(lst, maxdigits=28)

Convert list representing A. or G. progression to lange

>>> list2progression([0,1,2,...,9])
[0 1 .+. 9]
>>> list2progression([1,2,4,...,16])
[1 2 .*. 16]
>>> list2progression([1,2,4,...])
[1 2 .*. ∞]
>>> list2progression([1,-2,4,...])
[1 -2 .*. ∞]
>>> list2progression([1,-2,4,...,64]).l
[1, -2, 4, -8, 16, -32]
>>> list2progression([])
[]
>>> list2progression([1])
[1]
>>> list2progression([1,2])
[1 2]
>>> list2progression([1,2,3])
[1 2 3]
>>> list2progression([1,2,3,4])
[1 2 3 4]
>>> list2progression([1,2,3,4,5])
[1 2 3 4 5]
>>> list2progression([1,2,3,4,5,6])
[1 2 3 4 5 6]

Parameters

lst
 

Returns

Expand source code
def list2progression(lst, maxdigits=28):
    """Convert list representing A. or G. progression to lange

    >>> list2progression([0,1,2,...,9])
    [0 1 .+. 9]
    >>> list2progression([1,2,4,...,16])
    [1 2 .*. 16]
    >>> list2progression([1,2,4,...])
    [1 2 .*. ∞]
    >>> list2progression([1,-2,4,...])
    [1 -2 .*. ∞]
    >>> list2progression([1,-2,4,...,64]).l
    [1, -2, 4, -8, 16, -32]
    >>> list2progression([])
    []
    >>> list2progression([1])
    [1]
    >>> list2progression([1,2])
    [1 2]
    >>> list2progression([1,2,3])
    [1 2 3]
    >>> list2progression([1,2,3,4])
    [1 2 3 4]
    >>> list2progression([1,2,3,4,5])
    [1 2 3 4 5]
    >>> list2progression([1,2,3,4,5,6])
    [1 2 3 4 5 6]

    Parameters
    ----------
    lst

    Returns
    -------

    """
    if ... not in lst:  # pragma: no cover
        from lange.ap import AP

        return AP(*lst)  # Use AP as fallback for undefined progression.
    if len(lst) not in [4, 5] or lst[3] is not ...:  # pragma: no cover
        raise Exception(
            f"Cannot guess if you want an arithmetic or a geometric progression. Provide 3 numbers followed by '...', not {lst}."
        )

    # Protect diffs and ratios from floating point inequality issues (e.g. 0.8 - 0.6 != 0.2).
    precision = max(map(partial(detect_precision, maxdigits=maxdigits), lst))
    decctx = dec.Context()
    decctx.prec = precision
    lst_dec = [decctx.create_decimal(x) for x in lst if x is not ...]

    # Calculate diffs and ratios.
    try:
        diff1 = lst_dec[1] - lst_dec[0]
        diff2 = lst_dec[2] - lst_dec[1]
        ratio1 = nan if lst_dec[0] == 0 else lst_dec[1] / lst_dec[0]
        ratio2 = nan if lst_dec[1] == 0 else lst_dec[2] / lst_dec[1]
    except:  # pragma: no cover
        raise InconsistentLange(f"Cannot identify whether this is a G. or A. progression: {lst}")
    newlst = lst[0:2] + lst[3:]

    if diff1 == diff2:
        from lange.ap import AP

        return AP(*newlst)
    elif ratio1 == ratio2:
        from lange.gp import GP

        return GP(*newlst)
    else:  # pragma: no cover
        raise InconsistentLange(
            f"Cannot identify whether this is a G. or A. Progression: {lst}", diff1, diff2, ratio1, ratio2
        )

Classes

class InconsistentLange (*args, **kwargs)

Common base class for all non-exit exceptions.

Expand source code
class InconsistentLange(Exception):
    pass

Ancestors

  • builtins.Exception
  • builtins.BaseException