Пример #1
0
def test_rational_ceil():
    x = Rational(5, 2)
    y = Rational(-1, 2)
    z = Rational(1, 1)
    assert math.ceil(x) == 3
    assert math.ceil(y) == 0
    assert math.ceil(z) == 1
Пример #2
0
def _farey_algorithm_denominator(x, max_denominator=1000):
    """Find a rational approximation of x with denominator no larger than that specified.

    We use an algorithm based on the Farey sequence, which consists of all completely
    reduced fractions between 0 and 1. This ensures that the rational approximation
    found by the algorithm is completely reduced.

    To speed up the process for small x, note that we only need to look in the range between
    (0, 1) and (1, N), for N = math.floor(1 / x), since we will always reach this region
    eventually by repeated Farey addition.
    """

    N = math.floor(1 / x)
    if max_denominator <= N:
        return min(
            [Rational(0, 1), Rational(1, max_denominator)],
            key=lambda r: abs(x - r))

    left = Rational(0, 1)
    right = Rational(1, N)

    while max(left.denominator, right.denominator) < max_denominator:
        mediant = farey_add(left, right)
        if mediant.denominator > max_denominator:
            # the current bounds are as good as we can do, so have to choose the best of them
            break
        if x < mediant:
            right = mediant
        elif x > mediant:
            left = mediant
        else:
            return mediant

    return min([left, right], key=lambda r: abs(x - r))
Пример #3
0
def test_rational_floor():
    x = Rational(5, 2)
    y = Rational(-1, 2)
    z = Rational(1, 1)
    assert math.floor(x) == 2
    assert math.floor(y) == -1
    assert math.floor(z) == 1
Пример #4
0
def _farey_algorithm_accuracy(x, places=7):
    """Find a rational approximation of x to the specified number of decimal places.

    We use an algorithm based on the Farey sequence, which consists of all completely
    reduced fractions between 0 and 1. This ensures that the rational approximation
    found by the algorithm is completely reduced.

    To speed up the process for small x, note that we only need to look in the range between
    (0, 1) and (1, N), for N = math.floor(1 / x), since we will always reach this region
    eventually by repeated Farey addition.
    """

    left = Rational(0, 1)
    right = Rational(1, math.floor(1 / x))
    if almost_equal(x, left, places):
        return left
    elif almost_equal(x, right, places):
        return right

    mediant = None

    while mediant is None or not almost_equal(x, mediant, places):
        mediant = farey_add(left, right)
        if x < mediant:
            right = mediant
        elif x > mediant:
            left = mediant
        else:
            return mediant

    return mediant
Пример #5
0
def test_scf_from_rational():
    r = Rational(10, 7)
    result = SimpleContinuedFraction.from_rational(r)
    assert result == SimpleContinuedFraction(1, 2, 3)

    r = Rational(-19, 5)
    result = SimpleContinuedFraction.from_rational(r)
    assert result == SimpleContinuedFraction(-4, 5)
    # check that Rational and SimpleContinuedFraction objects not equal
    assert result != r
Пример #6
0
def test_rational_reduction():
    x = Rational(1, 3)
    y = Rational(2, 6)
    z = Rational(0, 3)

    assert x != y

    assert x.is_reduced
    assert not y.is_reduced
    assert not z.is_reduced
    assert y.reduced_form == x
    assert z.reduced_form == Rational(0, 1)
Пример #7
0
def test_rational_addition():
    x = Rational(1, 2)  # 1/2
    y = Rational(3, 5)  # 3/5

    # Rational + Rational -> Rational
    assert x + y == Rational(11, 10)
    # Rational + int -> Rational
    assert x + 1 == 1 + x == Rational(3, 2)
    # Rational + float -> float
    assert x + 0.1 == 0.1 + x == 0.6

    with raises(TypeError) as excinfo:
        _ = x + "a"
    assert str(excinfo.value) == "must be int, float or Rational, not str"
Пример #8
0
def test_rational_multiplication():
    x = Rational(1, 2)
    y = Rational(3, 5)

    # Rational * Rational -> Rational
    assert x * y == Rational(3, 10)
    # Rational * int -> Rational
    assert x * 4 == 4 * x == Rational(4, 2)
    # Rational * float -> float
    assert x * 3.2 == 3.2 * x == 1.6

    with raises(TypeError) as excinfo:
        _ = x * "a"
    assert str(excinfo.value) == "must be int, float or Rational, not str"
Пример #9
0
def test_rational_comparison():
    x = Rational(1, 2)
    y = Rational(2, 3)

    assert x < y and y > x
    assert 0 < x < 1

    with raises(TypeError) as excinfo:
        _ = x < "1"
    assert str(excinfo.value) == "must be int, float or Rational, not str"

    with raises(TypeError) as excinfo:
        _ = x > "0"
    assert str(excinfo.value) == "must be int, float or Rational, not str"
Пример #10
0
def _continued_fraction_algorithm_accuracy(x, places=7):
    epsilon = 0.5 * 10**-places
    n = 0
    current_convergent = Rational(math.floor(x), 1)
    if almost_equal(x, current_convergent, places=places):
        return current_convergent

    while True:
        next_truncation = truncated_continued_fraction(x, n + 1)
        next_convergent = next_truncation.as_rational
        if almost_equal(x, next_convergent, places=places):
            # we're within the allowed bound, but may be able to find a convergent
            # with smaller denominator also within the bound by reducing the last
            # value of the continued fraction.
            bound = x + (1 if n % 2 == 0 else -1) * epsilon
            optimal_reduction_factor = math.floor(
                (next_convergent.numerator -
                 next_convergent.denominator * bound) /
                (current_convergent.numerator -
                 current_convergent.denominator * bound))
            a_n_plus_one = next_truncation.last_value
            next_truncation = next_truncation.replace_last_value(
                a_n_plus_one - optimal_reduction_factor)
            return next_truncation.as_rational
        else:
            n += 1
            current_convergent = next_convergent
Пример #11
0
def _continued_fraction_algorithm_denominator(x, max_denominator=1000):
    """Find a rational approximation of x with denominator no larger than that specified.

    We use an algorithm based on truncating the continued fraction representation of a number,
    and reducing its final value until reaching a suitable rational representation,
    cf. https://en.wikipedia.org/wiki/Continued_fraction#Best_rational_approximations.
    """
    n = 0
    prev_denominator = 0  # k_{-1} = 0
    current_convergent = Rational(math.floor(x), 1)

    while True:
        next_truncation = truncated_continued_fraction(x, n + 1)
        next_convergent = next_truncation.as_rational
        if next_convergent == current_convergent:
            # reached the end of finite continued fraction
            return current_convergent
        if next_convergent.denominator > max_denominator:
            # we've gone too far so need to find potential convergents by reducing the last value
            # of the continued fraction, without going past half of a_{n+1}.
            # The smallest we can make the denominator in this way is given by
            # math.ceil(a_{n+1} / 2) * k_n + k_{n-1}
            a_n_plus_one = next_truncation.last_value
            smallest_denominator = (
                math.ceil(a_n_plus_one / 2) * current_convergent.denominator +
                prev_denominator)
            if smallest_denominator > max_denominator:
                # we can't get better than the approximation we already have
                return current_convergent
            else:
                # there is some i for which the reduced denominator is less than max_denominator
                # k'_{n+1} = (a_{n+1} - i) * k_n + k_{n-1} < max_denominator
                # The smallest such i is math.ceil((k_{n+1} - max_denominator) / k_n)
                optimal_reduction_factor = math.ceil(
                    (next_convergent.denominator - max_denominator) /
                    current_convergent.denominator)
                next_truncation = next_truncation.replace_last_value(
                    a_n_plus_one - optimal_reduction_factor)
                next_convergent = next_truncation.as_rational
                # if a_{n+1} is even and i == a_{n+1} / 2, we need to check the errors
                if (a_n_plus_one % 2 == 0
                        and optimal_reduction_factor == a_n_plus_one / 2):
                    current_error = abs(x - current_convergent)
                    next_error = abs(x - next_convergent)
                    if next_error < current_error:
                        return next_convergent
                    else:
                        return current_convergent
                else:
                    return next_convergent
        else:
            n += 1
            prev_denominator = current_convergent.denominator
            current_convergent = next_convergent
Пример #12
0
def test_rational_power():
    x = Rational(2, 3)
    # Rational ** int -> Rational
    assert x**3 == Rational(8, 27)
    assert x**-3 == Rational(27, 8)
    assert x**0 == Rational(1, 1)
    # Rational ** float -> float
    assert x**0.5 == 0.816496580927726
    # edge cases
    with raises(ZeroDivisionError):
        _ = Rational(0, 1)**-1
    assert Rational(0, 1)**0 == Rational(1, 1)
Пример #13
0
def test_rational_division():
    x = Rational(1, 2)
    y = Rational(3, 5)
    z = Rational(-4, 7)

    # Rational / (non-zero) Rational -> Rational
    assert x / y == Rational(5, 6)
    assert x / z == Rational(-7, 8)
    with raises(ZeroDivisionError):
        _ = x / Rational(0, 1)

    # Rational / (non-zero) int -> Rational
    assert x / 2 == Rational(1, 4)
    assert x / -2 == Rational(-1, 4)
    with raises(ZeroDivisionError):
        _ = x / 0

    # Rational / float -> float
    assert y / 1.2 == 0.5
Пример #14
0
def best_rational_approximation(x,
                                method="farey",
                                places=None,
                                max_denominator=None):
    """Find a rational approximation of x to the specified number of decimal places.

    We use an algorithm based on the Farey sequence, which consists of all completely
    reduced fractions between 0 and 1. This ensures that the rational approximation
    found by the algorithm is completely reduced.
    """
    if x == 0:
        return Rational(0, 1)
    elif x < 0:
        return -best_rational_approximation(-x, method, places,
                                            max_denominator)
    elif x >= 1:
        return int(x // 1) + best_rational_approximation(
            x % 1, method, places, max_denominator)
    elif 0.5 < x < 1:
        return 1 - best_rational_approximation(1 - x, method, places,
                                               max_denominator)

    if method == "farey":
        if places is not None and max_denominator is None:
            return _farey_algorithm_accuracy(x, places)
        if places is None and max_denominator is not None:
            return _farey_algorithm_denominator(x, max_denominator)
        else:
            raise ValueError("must specify one of places or max_denominator")
    elif method == "continued_fraction":
        if places is not None and max_denominator is None:
            return _continued_fraction_algorithm_accuracy(x, places)
        elif places is None and max_denominator is not None:
            return _continued_fraction_algorithm_denominator(
                x, max_denominator)
        else:
            raise ValueError("must specify one of places or max_denominator")
    else:
        raise ValueError("method should be one of %s" % ALLOWED_METHODS)
Пример #15
0
def test_rational_repr():
    x = Rational(1, 2)
    assert repr(x) == "1/2"
Пример #16
0
def farey_add(x: Rational, y: Rational) -> Rational:
    """ Find the mediant of two rational numbers, as a rational number. """
    return Rational(x.numerator + y.numerator, x.denominator + y.denominator)
Пример #17
0
def test_rational_subtraction():
    x = Rational(1, 2)  # 1/2
    y = Rational(3, 5)  # 3/5
    assert x - y == Rational(-1, 10)
Пример #18
0
def test_rational_negative():
    x = Rational(1, 2)
    neg_x = -x
    assert neg_x == Rational(-1, 2)
    assert neg_x.is_negative
Пример #19
0
def test_rational_float():
    x = Rational(1, 2)
    assert float(x) == 0.5
Пример #20
0
def test_scf_as_rational():
    x = SimpleContinuedFraction(1, 2, 3)  # 1 + 1 / (2 + 1 / 3) = 10/7
    assert x.as_rational == Rational(10, 7)

    y = SimpleContinuedFraction(2)  # 2
    assert y.as_rational == Rational(2, 1)
Пример #21
0
def test_rational_zero():
    x = Rational(0, 1)
    assert x.is_zero
    with raises(ZeroDivisionError):
        _ = x.inverse
Пример #22
0
def test_rational_inverse():
    x = Rational(1, 3)
    assert x.inverse == Rational(3, 1)

    x = Rational(-2, 5)
    assert x.inverse == Rational(-5, 2)