94 lines
3.5 KiB
Python
94 lines
3.5 KiB
Python
"""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<TheoryDataRow<double, double, int, string>> 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()
|