|
| 1 | +def calculate_pi(limit: int) -> str: |
| 2 | + """ |
| 3 | + https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80 |
| 4 | + Leibniz Formula for Pi |
| 5 | +
|
| 6 | + The Leibniz formula is the special case arctan 1 = 1/4 Pi . |
| 7 | + Leibniz's formula converges extremely slowly: it exhibits sublinear convergence. |
| 8 | +
|
| 9 | + Convergence (https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80#Convergence) |
| 10 | +
|
| 11 | + We cannot try to prove against an interrupted, uncompleted generation. |
| 12 | + https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80#Unusual_behaviour |
| 13 | + The errors can in fact be predicted; |
| 14 | + but those calculations also approach infinity for accuracy. |
| 15 | +
|
| 16 | + Our output will always be a string since we can defintely store all digits in there. |
| 17 | + For simplicity' sake, let's just compare against known values and since our outpit |
| 18 | + is a string, we need to convert to float. |
| 19 | +
|
| 20 | + >>> import math |
| 21 | + >>> float(calculate_pi(15)) == math.pi |
| 22 | + True |
| 23 | +
|
| 24 | + Since we cannot predict errors or interrupt any infinite alternating |
| 25 | + series generation since they approach infinity, |
| 26 | + or interrupt any alternating series, we are going to need math.isclose() |
| 27 | +
|
| 28 | + >>> math.isclose(float(calculate_pi(50)), math.pi) |
| 29 | + True |
| 30 | +
|
| 31 | + >>> math.isclose(float(calculate_pi(100)), math.pi) |
| 32 | + True |
| 33 | +
|
| 34 | + Since math.pi-constant contains only 16 digits, here some test with preknown values: |
| 35 | +
|
| 36 | + >>> calculate_pi(50) |
| 37 | + '3.14159265358979323846264338327950288419716939937510' |
| 38 | + >>> calculate_pi(80) |
| 39 | + '3.14159265358979323846264338327950288419716939937510582097494459230781640628620899' |
| 40 | +
|
| 41 | + To apply the Leibniz formula for calculating pi, |
| 42 | + the variables q, r, t, k, n, and l are used for the iteration process. |
| 43 | + """ |
| 44 | + q = 1 |
| 45 | + r = 0 |
| 46 | + t = 1 |
| 47 | + k = 1 |
| 48 | + n = 3 |
| 49 | + l = 3 |
| 50 | + decimal = limit |
| 51 | + counter = 0 |
| 52 | + |
| 53 | + result = "" |
| 54 | + |
| 55 | + """ |
| 56 | + We will avoid using yield since we otherwise get a Generator-Object, |
| 57 | + which we can't just compare against anything. We would have to make a list out of it |
| 58 | + after the generation, so we will just stick to plain return logic: |
| 59 | + """ |
| 60 | + while counter != decimal + 1: |
| 61 | + if 4 * q + r - t < n * t: |
| 62 | + result += str(n) |
| 63 | + if counter == 0: |
| 64 | + result += "." |
| 65 | + |
| 66 | + if decimal == counter: |
| 67 | + break |
| 68 | + |
| 69 | + counter += 1 |
| 70 | + nr = 10 * (r - n * t) |
| 71 | + n = ((10 * (3 * q + r)) // t) - 10 * n |
| 72 | + q *= 10 |
| 73 | + r = nr |
| 74 | + else: |
| 75 | + nr = (2 * q + r) * l |
| 76 | + nn = (q * (7 * k) + 2 + (r * l)) // (t * l) |
| 77 | + q *= k |
| 78 | + t *= l |
| 79 | + l += 2 |
| 80 | + k += 1 |
| 81 | + n = nn |
| 82 | + r = nr |
| 83 | + return result |
| 84 | + |
| 85 | + |
| 86 | +def main() -> None: |
| 87 | + print(f"{calculate_pi(50) = }") |
| 88 | + import doctest |
| 89 | + |
| 90 | + doctest.testmod() |
| 91 | + |
| 92 | + |
| 93 | +if __name__ == "__main__": |
| 94 | + main() |
0 commit comments