added root functions
.NET Test / .NET tests (push) Successful in 1m31s

This commit is contained in:
2026-09-16 20:24:08 +04:00
parent 24d2aef892
commit 7084fae174
11 changed files with 1912 additions and 94 deletions
@@ -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()