Files
Just.PreciseMath/1-tests/Just.PreciseMath.Tests/ReferenceData/generate_exp.py
T
just b54a0a2d42
.NET Test / .NET tests (push) Successful in 1m31s
pow, log and exp
2026-09-15 00:33:31 +04:00

64 lines
3.2 KiB
Python

"""Regenerate Exp reference rows using only Python's standard library.
Decimal.exp/ln are correctly rounded. Evaluate exact stored binary64 sums at
180 and 260 digits and require identical 120-digit references and split inputs.
No DD implementation, binary64 exp, or DD-rounded mathematical constants are used.
Run from the repo root; stdout is the body of ExpReferenceData.Cases.
"""
from decimal import Decimal, localcontext
import math
import random
def generate(precision):
with localcontext() as context:
context.prec = precision
two = Decimal(2)
ln2 = two.ln()
high_ln2 = float(ln2)
low_ln2 = float(ln2 - Decimal.from_float(high_ln2))
tail_ln2 = float(ln2 - Decimal.from_float(high_ln2) - Decimal.from_float(low_ln2))
inputs = [(x, 0.0) for x in [1.0, -1.0, 0.5, -0.5, 709.5, 709.781, -740.0,
-745.131, 710.0, -746.0, 1e-30, -1e-30, 5e-324, -5e-324]]
for high in [1.0, -1.0, 0.5, -0.5, 709.5, -740.0]:
for low in [math.ulp(high) / 4, -math.ulp(high) / 4, 5e-324, -5e-324]:
inputs.append((high, low))
rng = random.Random(271828)
for _ in range(240):
high = rng.uniform(-745, 709.78)
inputs.append((high, rng.choice([-1, 0, 1]) * math.ulp(high) / 4))
# Neighbors of binary range-reduction half steps, covering both signs of k.
for k in [-1074, -1022, -100, -1, 0, 1, 100, 1023]:
midpoint = (Decimal(k) + Decimal('0.5')) * ln2
high = float(midpoint)
low = float(midpoint - Decimal.from_float(high))
inputs.extend((high, residual) for residual in [math.nextafter(low, -math.inf), low, math.nextafter(low, math.inf)])
# Adjacent representable low components straddling true overflow and
# half-minimum-subnormal thresholds. Min-normal neighbors pin gradual underflow.
for boundary in [(two ** 1024 - two ** 970).ln(), -1075 * ln2, -1022 * ln2]:
high = float(boundary)
low = float(boundary - Decimal.from_float(high))
inputs.extend((high, residual) for residual in [math.nextafter(low, -math.inf), low, math.nextafter(low, math.inf)])
rows = []
for high, low in inputs:
# 1100 digits preserve even a minimum-subnormal low beside these
# bounded highs. Decimal.exp consumes this exact input before rounding.
with localcontext() as input_context:
input_context.prec = 1100
exact = Decimal.from_float(high) + Decimal.from_float(low)
result = exact.exp()
overflow = result >= two ** 1024 - two ** 970
underflow = result <= two ** -1075
with localcontext() as output_context:
output_context.prec = 120
reference = format(+result, 'e')
rows.append(f' yield return new({high!r}, {low!r}, "{reference}", {str(overflow).lower()}, {str(underflow).lower()});')
return tail_ln2, rows
if __name__ == '__main__':
first = generate(180)
assert first == generate(260), 'References changed with increased precision'
print(f'// ln(2) third component: {first[0]!r}')
print('\n'.join(first[1]))