codexpast-exam-of-the-year-1-of-the-computer-science-course-of-the-university-of-oxford2022imperative-programming-resit-error-work.py
past-exam-of-the-year-1-of-the-computer-science-course-of-the-university-of-oxford/2022/imperative-programming-resit-error-work.py
"""Original ideal-precision work comparison. Python 3.14.4, Matplotlib 3.10.7.
Reference errors use Decimal at 80 digits. Counts are for the solution's exact
loop structures, counting each multiplication and division once, not bit cost.
The image is written to the current working directory.
"""
from decimal import Decimal, localcontext
from pathlib import Path
import json
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
def accuracy_work():
with localcontext() as context:
context.prec = 80
e = Decimal(1).exp()
def ea(n):
a = Decimal(n)
return (1 + 1/a) ** n
def ec(n):
a = Decimal(n)
return (a+1)*(1+1/a)**n-a*(1+1/(a-1))**(n-1)
def threshold(f, start, tolerance):
if abs(f(start)-e) <= tolerance:
return start
low, high = start, 2*start
while abs(f(high)-e) > tolerance:
low, high = high, 2*high
while high-low > 1:
middle = low+(high-low)//2
if abs(f(middle)-e) <= tolerance:
high = middle
else:
low = middle
return high
rows = []
for digits in range(1, 9):
tolerance = Decimal(5)*Decimal(10)**(-digits-1)
na = threshold(ea, 1, tolerance)
nc = threshold(ec, 2, tolerance)
nb, term, total = 0, Decimal(1), Decimal(1)
while abs(total-e) > tolerance:
nb += 1
term /= nb
total += term
assert na <= 2**31-1
assert abs(ea(na)-e) <= tolerance
assert na == 1 or abs(ea(na-1)-e) > tolerance
assert abs(ec(nc)-e) <= tolerance
assert nc == 2 or abs(ec(nc-1)-e) > tolerance
rows.append({"digits":digits,"ea_n":na,"eb_n":nb,"ec_n":nc,
"ea_work":na.bit_length()+na.bit_count()+1,
"eb_work":nb,
"ec_work":2*(nc-1).bit_length()+2*(nc-1).bit_count()+5,
"ea_reference_error":str(abs(ea(na)-e)),
"eb_reference_error":str(abs(total-e)),
"ec_reference_error":str(abs(ec(nc)-e))})
return rows
def main():
rows = accuracy_work()
fig, ax = plt.subplots(figsize=(8.3, 4.5), layout="constrained")
styles = [("ea_work","Fast binomial power","#276792","o"),
("ec_work","Parallel powers and subtraction","#a5483c","s"),
("eb_work","Reciprocal-factorial sum","#2e874b","^")]
for key, label, color, marker in styles:
ax.plot([r["digits"] for r in rows],[r[key] for r in rows],
color=color,marker=marker,label=label)
ax.set(xlabel="Requested decimal places d (absolute error ≤ 0.5 × 10⁻ᵈ)",
ylabel="Multiplications + divisions",
title="1(d): arithmetic work with high-precision reference errors",
xticks=list(range(1,9)),ylim=(0,65))
ax.grid(axis="y",color=".88",lw=.7)
ax.legend(loc="upper left",fontsize=9)
ax.text(.98,.04,"Ideal-precision comparison; Double rounding can alter accuracy",
transform=ax.transAxes,ha="right",fontsize=8)
fig.savefig(Path(__file__).with_suffix(".png").name,dpi=100,
facecolor="white",transparent=False)
print(json.dumps(rows))
if __name__ == "__main__":
main()