78 lines
3.0 KiB
Python
78 lines
3.0 KiB
Python
"""Print BasicArithmeticMatchesPrecomputedIrrationalResults InlineData.
|
|
|
|
Run with Python 3.11+; standard library only. Decimal supplies irrational
|
|
constants; Fraction supplies exact arithmetic on their stored binary64 pairs.
|
|
The test suite consumes the literals, not this script or Python at runtime.
|
|
"""
|
|
|
|
from decimal import Decimal, localcontext
|
|
from fractions import Fraction
|
|
|
|
|
|
def arctan_inverse(inverse: int) -> Decimal:
|
|
"""Evaluate atan(1/inverse) using its alternating power series."""
|
|
x = Decimal(1) / inverse
|
|
power = x
|
|
total = x
|
|
index = 1
|
|
while True:
|
|
power *= -(x * x)
|
|
updated = total + power / (2 * index + 1)
|
|
if updated == total:
|
|
return total
|
|
total = updated
|
|
index += 1
|
|
|
|
|
|
def split(value: Fraction) -> tuple[float, float]:
|
|
"""Round high, then the exact residual, to nearest-even binary64."""
|
|
high = float(value)
|
|
return high, float(value - Fraction(high))
|
|
|
|
|
|
def fixtures(precision: int) -> list[tuple[str, list[float]]]:
|
|
with localcontext() as context:
|
|
context.prec = precision
|
|
constants = {
|
|
"pi": 16 * arctan_inverse(5) - 4 * arctan_inverse(239),
|
|
"e": Decimal(1).exp(),
|
|
"sqrt2": Decimal(2).sqrt(),
|
|
"sqrt3": Decimal(3).sqrt(),
|
|
"ln2": Decimal(2).ln(),
|
|
"phi": (1 + Decimal(5).sqrt()) / 2,
|
|
}
|
|
pairs = {name: split(Fraction(value)) for name, value in constants.items()}
|
|
|
|
rows = []
|
|
for left_name, right_name in [("pi", "e"), ("sqrt2", "sqrt3"), ("ln2", "phi")]:
|
|
for overload in ["DD/DD", "DD/double", "double/DD"]:
|
|
left_high, left_low = pairs[left_name]
|
|
right_high, right_low = pairs[right_name]
|
|
if overload == "DD/double":
|
|
right_low = 0.0
|
|
if overload == "double/DD":
|
|
left_low = 0.0
|
|
left = Fraction(left_high) + Fraction(left_low)
|
|
right = Fraction(right_high) + Fraction(right_low)
|
|
values = [left_high, left_low, right_high, right_low]
|
|
for expected in [left + right, left - right, left * right, left / right]:
|
|
high, low = split(expected)
|
|
# These precomputed references are much closer than the test's
|
|
# 2^-100 bound; this does not assert library correct rounding.
|
|
assert abs(Fraction(high) + Fraction(low) - expected) <= abs(expected) / (1 << 105)
|
|
values.extend([high, low])
|
|
rows.append((f"{left_name}, {right_name}: {overload}", values))
|
|
return rows
|
|
|
|
|
|
if __name__ == "__main__":
|
|
rows = fixtures(160)
|
|
assert rows == fixtures(240), "Increase precision: binary64 fixtures did not stabilize"
|
|
for label, values in rows:
|
|
print(f" // {label}")
|
|
overload = label.split(": ")[1]
|
|
print(f' [InlineData("{overload}",')
|
|
for index in range(0, len(values), 2):
|
|
suffix = ")]" if index == len(values) - 2 else ","
|
|
print(f" {values[index]!r}, {values[index + 1]!r}{suffix}")
|