"""Generate independent RootN fixtures using only Python's standard library. Run: python3 path/to/generate_rootn.py [--check]. Exact binary64 input sums are formed at 2200 digits and verified with Fraction. Decimal ln/exp at 450 and 650 digits must agree after rounding to 120 significant decimal digits. Sparse corrections below that reference precision have separate exact tests. """ from decimal import Decimal, localcontext from fractions import Fraction from pathlib import Path import argparse import math import random import sys OUTPUT = Path(__file__).with_name('RootNReferenceData.cs') def reference(high, low, degree, precision): with localcontext() as context: context.prec = 2200 value = Decimal.from_float(high) + Decimal.from_float(low) assert Fraction(value) == Fraction(high) + Fraction(low) negative = value < 0 assert not negative or degree % 2 with localcontext() as context: context.prec = precision result = (abs(value).ln() / degree).exp() if negative: result = -result with localcontext() as rounded_context: rounded_context.prec = 120 rounded = +result assert abs(result - rounded) <= abs(rounded) * Decimal(2) ** -350 return format(rounded, 'e') def inputs(): degrees = [-2147483648, -2147483647, -1000000000, -65537, -127, -4, 4, 127, 65537, 1000000000, 2147483646, 2147483647] for high in [math.ulp(0.0), 3 * math.ulp(0.0), sys.float_info.min, math.nextafter(sys.float_info.min, 0.0), 1e-308, 0.5, math.nextafter(1.0, 0.0), 1.0, math.nextafter(1.0, math.inf), 2.0, 81.0, 1e308, sys.float_info.max]: for low in [0.0, math.ulp(high) / 4, -math.ulp(high) / 4]: for degree in degrees: yield high, low, degree rng = random.Random(20260916) for _ in range(192): high = math.ldexp(rng.uniform(1.0, 1.999), rng.randint(-1074, 1023)) low = rng.choice([-1, 0, 1]) * math.ulp(high) / 4 degree = rng.choice(degrees + [rng.randint(4, 2147483647), -rng.randint(4, 2147483648)]) if degree % 2 and rng.randrange(2): high, low = -high, -low yield high, low, degree def generate(): rows = [] for high, low, degree in dict.fromkeys(inputs()): first = reference(high, low, degree, 450) assert first == reference(high, low, degree, 650), (high, low, degree) rows.append(f' yield return new({high!r}, {low!r}, {degree}, "{first}");') content = '''// Generated by generate_rootn.py; do not hand-edit reference literals. // Exact component sums; Decimal ln/exp at 450/650 digits, rounded to 120 digits. using Xunit; namespace Just.PreciseMath.Tests.ReferenceData; internal static class RootNReferenceData { internal static IEnumerable> Cases() { ''' + '\n'.join(rows) + '\n }\n}\n' return content, len(rows) def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--check', action='store_true') args = parser.parse_args() content, count = generate() if args.check: assert OUTPUT.read_text() == content, 'Fixture is stale; regenerate it' print(f'Verified {count} RootN references at 450/650 digits with exact input sums.') else: OUTPUT.write_text(content) print(f'Generated {count} RootN references in {OUTPUT.name}.') if __name__ == '__main__': main()