@@ -0,0 +1,93 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user