"""Generate Log fixtures independently with Python's standard library. Run from any directory: python3 path/to/generate_log.py [--check]. Construct exact binary64 component sums at 2200 decimal digits, verify against Fraction, and require identical 120-digit Decimal.ln references at 450/650 digits. The C# suite also multiplies the independent ln(2) reference by integer exponents using BigInteger to check powers of two across every binary64 input exponent. """ 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('LogReferenceData.cs') def exact_sum(high, low): with localcontext() as context: context.prec = 2200 result = Decimal.from_float(high) + Decimal.from_float(low) assert Fraction(result) == Fraction(high) + Fraction(low), (high, low) return result def reference(high, low, precision): value = exact_sum(high, low) assert value > 0 with localcontext() as context: context.prec = precision result = value.ln() with localcontext() as output_context: output_context.prec = 120 rounded = +result assert abs(result - rounded) <= abs(rounded) * Decimal(2) ** -350 return format(rounded, 'e') def inputs(): for high in [0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 3.0, 10.0, 1e-308, 1e308, sys.float_info.max, sys.float_info.min, math.ulp(0.0), 3 * math.ulp(0.0), math.nextafter(sys.float_info.min, 0.0), math.nextafter(sys.float_info.min, math.inf), math.nextafter(1.0, 0.0), math.nextafter(1.0, math.inf)]: yield 'scalar-boundary', high, 0.0 for scale in [-1074, -1073, -1022, -1000, -500, -1, 0, 1, 500, 1000, 1023]: high = math.ldexp(1.5, scale) for low in [math.ulp(high) / 4, -math.ulp(high) / 4]: yield 'binade-residual', high, low for high in [1.0, math.sqrt(2), 1 / math.sqrt(2), 1e-308, 1e308, sys.float_info.max]: for low in [math.ulp(high) / 4, -math.ulp(high) / 4, math.ulp(0.0), -math.ulp(0.0)]: yield 'dense-and-sparse-low', high, low # Same high with adjacent lows crosses the tiny-delta kernel cutoff. for low in [2.0 ** -54, -2.0 ** -54]: for neighbor in [math.nextafter(low, -math.inf), low, math.nextafter(low, math.inf)]: yield 'tiny-delta-transition', 1.0, neighbor for scale in [-53, -54, -55, -100, -500, -1000, -1022, -1073, -1074]: for sign in [-1, 1]: yield 'near-one-log', 1.0, sign * 2.0 ** scale # Centering transitions in both high and low, far from x=1 cancellation. for high in [math.sqrt(2), 1 / math.sqrt(2)]: for neighbor in [math.nextafter(high, 0.0), high, math.nextafter(high, math.inf)]: for low in [math.ulp(neighbor) / 4, -math.ulp(neighbor) / 4]: yield 'centering-transition', neighbor, low rng = random.Random(3141592) for index in range(256): high = math.ldexp(rng.uniform(1.0, 1.999), rng.randint(-1074, 1023)) yield f'random-{index:03}', high, rng.choice([-1, 0, 1]) * math.ulp(high) / 4 def generate(): ln2 = reference(2.0, 0.0, 450) assert ln2 == reference(2.0, 0.0, 650) rows = [] for label, high, low in inputs(): first = reference(high, low, 450) assert first == reference(high, low, 650), (label, high, low) rows.append(f' yield return new("{label}", {high!r}, {low!r}, "{first}");') content = '''// Generated by generate_log.py; do not hand-edit reference literals. // Exact binary64 sums; Decimal.ln at 450/650 digits, rounded to 120 digits. using Xunit; namespace Just.PreciseMath.Tests.ReferenceData; internal static class LogReferenceData { ''' + f' internal const string Ln2 = "{ln2}";\n\n' + ''' 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', help='Verify fixtures without writing') args = parser.parse_args() content, count = generate() if args.check: assert OUTPUT.read_text() == content, 'Fixture is stale; regenerate it' print(f'Verified {count} Log references and ln(2) at 450/650 digits with exact input sums.') else: OUTPUT.write_text(content) print(f'Generated {count} Log references in {OUTPUT.name}.') if __name__ == '__main__': main()