The Algorithms logo
The Algorithms
AboutDonate

Pi Generator

J
def calculate_pi(limit: int) -> str:
    """
    https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80
    Leibniz Formula for Pi

    The Leibniz formula is the special case arctan 1 = 1/4 Pi .
    Leibniz's formula converges extremely slowly: it exhibits sublinear convergence.

    Convergence (https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80#Convergence)

    We cannot try to prove against an interrupted, uncompleted generation.
    https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80#Unusual_behaviour
    The errors can in fact be predicted;
    but those calculations also approach infinity for accuracy.

    Our output will always be a string since we can defintely store all digits in there.
    For simplicity' sake, let's just compare against known values and since our outpit
    is a string, we need to convert to float.

    >>> import math
    >>> float(calculate_pi(15)) == math.pi
    True

    Since we cannot predict errors or interrupt any infinite alternating
    series generation since they approach infinity,
    or interrupt any alternating series, we are going to need math.isclose()

    >>> math.isclose(float(calculate_pi(50)), math.pi)
    True

    >>> math.isclose(float(calculate_pi(100)), math.pi)
    True

    Since math.pi-constant contains only 16 digits, here some test with preknown values:

    >>> calculate_pi(50)
    '3.14159265358979323846264338327950288419716939937510'
    >>> calculate_pi(80)
    '3.14159265358979323846264338327950288419716939937510582097494459230781640628620899'

    To apply the Leibniz formula for calculating pi,
    the variables q, r, t, k, n, and l are used for the iteration process.
    """
    q = 1
    r = 0
    t = 1
    k = 1
    n = 3
    l = 3
    decimal = limit
    counter = 0

    result = ""

    """
    We will avoid using yield since we otherwise get a Generator-Object,
    which we can't just compare against anything. We would have to make a list out of it
    after the generation, so we will just stick to plain return logic:
    """
    while counter != decimal + 1:
        if 4 * q + r - t < n * t:
            result += str(n)
            if counter == 0:
                result += "."

            if decimal == counter:
                break

            counter += 1
            nr = 10 * (r - n * t)
            n = ((10 * (3 * q + r)) // t) - 10 * n
            q *= 10
            r = nr
        else:
            nr = (2 * q + r) * l
            nn = (q * (7 * k) + 2 + (r * l)) // (t * l)
            q *= k
            t *= l
            l += 2
            k += 1
            n = nn
            r = nr
    return result


def main() -> None:
    print(f"{calculate_pi(50) = }")
    import doctest

    doctest.testmod()


if __name__ == "__main__":
    main()