addoed exponential functions
.NET Test / .NET tests (push) Successful in 1m32s

This commit is contained in:
2026-09-16 23:20:53 +04:00
parent 7084fae174
commit b5a8bd2580
9 changed files with 1255 additions and 86 deletions
+35 -53
View File
@@ -2,63 +2,45 @@ namespace Just.PreciseMath;
public static partial class DDMath public static partial class DDMath
{ {
/// <summary>Returns e raised to the specified double-double value.</summary> /// <inheritdoc cref="DoubleDouble.Exp"/>
/// <remarks>
/// Uses binary range reduction and a [12/12] Padé approximation. Results are
/// approximate, not guaranteed correctly rounded; tests check 2^-100 relative
/// error plus one minimum binary64 subnormal against high-precision references.
/// Precision decreases near underflow. NaN returns canonical NaN; positive
/// infinity returns positive infinity, negative infinity returns positive zero,
/// and either zero returns one. Both input components affect range boundaries.
/// </remarks>
[Pure] [Pure]
public static DoubleDouble Exp(DoubleDouble value) public static DoubleDouble Exp(DoubleDouble value)
{ {
if (double.IsNaN(value.High)) return DoubleDouble.Exp(value);
{ }
return DoubleDouble.NaN;
}
// These deliberately loose bounds only reject inputs safely outside the
// result range, even with a normalized low of either sign. They also keep
// infinities and huge finite inputs out of the floating-to-int conversion.
if (value.High > 710.0)
{
return new DoubleDouble(double.PositiveInfinity);
}
if (value.High < -746.0)
{
return DoubleDouble.Zero;
}
if (value.High == 0.0)
{
return DoubleDouble.One;
}
// The binary64 estimate need not choose the nearest k on a tie: either /// <inheritdoc cref="DoubleDouble.Exp2"/>
// neighbor leaves |r| < 0.347. k is bounded by [-1076, 1024]. Subtract [Pure]
// separate products to retain cancellation residuals. A third ln(2) public static DoubleDouble Exp2(DoubleDouble value)
// component prevents k times the DD constant error from dominating r. {
// Generated independently at 180/260 decimal digits by generate_exp.py. return DoubleDouble.Exp2(value);
const double ln2Tail = 5.707708438416212e-34; }
int exponent = (int)Math.Round(value.High / DoubleDouble.Ln2.High);
DoubleDouble reduced = value - (new DoubleDouble(DoubleDouble.Ln2.High) * exponent);
reduced -= new DoubleDouble(DoubleDouble.Ln2.Low) * exponent;
reduced -= new DoubleDouble(ln2Tail) * exponent;
// P(x)/P(-x), with exact binary64 integer coefficients. For n=12, /// <inheritdoc cref="DoubleDouble.Exp10"/>
// c_k = (24-k)!*12! / (24!*k!*(12-k)!), scaled by 1/c_12. [Pure]
// This retains the legacy approximation but not its unsafe E^k scaling. public static DoubleDouble Exp10(DoubleDouble value)
// Horner accumulators remain normal and finite on the reduced interval. {
ReadOnlySpan<double> coefficients = [156.0, 12012.0, 600600.0, 21621600.0, return DoubleDouble.Exp10(value);
588107520.0, 12350257920.0, 201132771840.0, 2514159648000.0, }
23465490048000.0, 154872234316800.0, 647647525324800.0, 1295295050649600.0];
DoubleDouble numerator = DoubleDouble.One; /// <inheritdoc cref="DoubleDouble.ExpM1"/>
DoubleDouble denominator = DoubleDouble.One; [Pure]
foreach (double coefficient in coefficients) public static DoubleDouble ExpM1(DoubleDouble value)
{ {
numerator = (numerator * reduced) + coefficient; return DoubleDouble.ExpM1(value);
denominator = (denominator * -reduced) + coefficient; }
}
return ScalePowerOfTwo(numerator / denominator, exponent); /// <inheritdoc cref="DoubleDouble.Exp2M1"/>
[Pure]
public static DoubleDouble Exp2M1(DoubleDouble value)
{
return DoubleDouble.Exp2M1(value);
}
/// <inheritdoc cref="DoubleDouble.Exp10M1"/>
[Pure]
public static DoubleDouble Exp10M1(DoubleDouble value)
{
return DoubleDouble.Exp10M1(value);
} }
} }
+1 -1
View File
@@ -70,7 +70,7 @@ public static partial class DDMath
result = 1.0 / result; result = 1.0 / result;
resultExponent = -resultExponent; resultExponent = -resultExponent;
} }
return ScalePowerOfTwo(negative ? -result : result, resultExponent); return PreciseMathHelper.ScalePowerOfTwo(negative ? -result : result, resultExponent);
} }
/// <summary>Raises a double-double value to a binary64 power.</summary> /// <summary>Raises a double-double value to a binary64 power.</summary>
-25
View File
@@ -89,29 +89,4 @@ public static partial class DDMath
{ {
return DoubleDouble.InvSqrt(value); return DoubleDouble.InvSqrt(value);
} }
// For finite, nonzero normalized significands and exponents bounded by the
// integer-power domain (|exponent| < 2^42). Keep the exponent separate until
// the final result so an intermediate cannot overflow before reciprocation.
private static DoubleDouble ScalePowerOfTwo(DoubleDouble value, long exponent)
{
long resultExponent = Math.ILogB(value.High) + exponent;
if (resultExponent > 1024)
{
return new DoubleDouble(Math.CopySign(double.PositiveInfinity, value.High));
}
if (resultExponent < -1075)
{
return new DoubleDouble(Math.CopySign(0.0, value.High));
}
if (resultExponent >= -969 && resultExponent <= 1022)
{
// Normal high and room for a dense low; a sparse low may underflow
// by at most half a minimum subnormal. Normalize its signed zero.
return DoubleDouble.FromComponents(Math.ScaleB(value.High, (int)exponent),
Math.ScaleB(value.Low, (int)exponent));
}
return PreciseMathHelper.ScalePowerOfTwoBoundary(value, exponent);
}
} }
@@ -0,0 +1,237 @@
namespace Just.PreciseMath;
public readonly partial struct DoubleDouble : IExponentialFunctions<DoubleDouble>
{
/// <summary>Returns e raised to the specified double-double value.</summary>
/// <remarks>
/// Uses binary range reduction and a [12/12] Padé approximation. Results are
/// approximate, not guaranteed correctly rounded; tests check 2^-100 relative
/// error plus one minimum binary64 subnormal against high-precision references.
/// Precision decreases near underflow. NaN returns canonical NaN; positive
/// infinity returns positive infinity, negative infinity returns positive zero,
/// and either zero returns one. Both input components affect range boundaries.
/// </remarks>
[Pure]
public static DoubleDouble Exp(DoubleDouble value)
{
if (double.IsNaN(value.High))
{
return DoubleDouble.NaN;
}
// These deliberately loose bounds only reject inputs safely outside the
// result range, even with a normalized low of either sign. They also keep
// infinities and huge finite inputs out of the floating-to-int conversion.
if (value.High > 710.0)
{
return new DoubleDouble(double.PositiveInfinity);
}
if (value.High < -746.0)
{
return DoubleDouble.Zero;
}
if (value.High == 0.0)
{
return DoubleDouble.One;
}
// The binary64 estimate need not choose the nearest k on a tie: either
// neighbor leaves |r| < 0.347. k is bounded by [-1076, 1024]. Subtract
// separate products to retain cancellation residuals. A third ln(2)
// component prevents k times the DD constant error from dominating r.
// Generated independently at 180/260 decimal digits by generate_exp.py.
const double ln2Tail = 5.707708438416212e-34;
int exponent = (int)Math.Round(value.High / DoubleDouble.Ln2.High);
DoubleDouble reduced = value - (new DoubleDouble(DoubleDouble.Ln2.High) * exponent);
reduced -= new DoubleDouble(DoubleDouble.Ln2.Low) * exponent;
reduced -= new DoubleDouble(ln2Tail) * exponent;
return PreciseMathHelper.ScalePowerOfTwo(ExpReduced(reduced), exponent);
}
/// <summary>Returns two raised to the specified double-double value.</summary>
/// <remarks>
/// Reduces in base two before evaluating a bounded natural exponential, avoiding
/// amplification of ln(2) rounding error by a large input. Integer powers in the
/// finite binary64 range are exact. Special values and approximate accuracy
/// follow <see cref="Exp"/>; both components determine range boundaries.
/// </remarks>
[Pure]
public static DoubleDouble Exp2(DoubleDouble value)
{
if (IsNaN(value))
{
return NaN;
}
if (value.High > 1024.0)
{
return PositiveInfinity;
}
if (value <= new DoubleDouble(-1075.0))
{
return Zero;
}
if (value < new DoubleDouble(-1074.0))
{
// Include sparse lows just above the exact half-subnormal tie;
// rounding exp(r) to one must not erase which side the input is on.
return Epsilon;
}
int exponent = (int)Math.Round(value.High);
DoubleDouble fraction = value - exponent;
if (fraction.High != 0.0 && Math.Abs(fraction.High) < Math.ScaleB(1.0, -500) && exponent <= 1023)
{
// 2^(k+d) = 2^k + 2^k*d*ln(2) + O(2^k*d²). Scale d to
// a bounded mantissa before multiplying: d*ln(2) may otherwise
// round in the subnormal range before 2^k restores the correction.
// The omitted term is < 2^-500 relative to the correction itself.
int adjustment = Math.ILogB(fraction.High);
DoubleDouble mantissa = FromComponents(Math.ScaleB(fraction.High, -adjustment),
Math.ScaleB(fraction.Low, -adjustment));
DoubleDouble correction = PreciseMathHelper.ScalePowerOfTwo(mantissa * Ln2, exponent + adjustment);
return new DoubleDouble(Math.ScaleB(1.0, exponent)) + correction;
}
DoubleDouble reduced = fraction * Ln2;
return PreciseMathHelper.ScalePowerOfTwo(ExpReduced(reduced), exponent);
}
/// <summary>Returns ten raised to the specified double-double value.</summary>
/// <remarks>
/// Subtracts three split log10(2) products before conversion to a bounded
/// natural exponent. Special values and approximate accuracy follow
/// <see cref="Exp"/>; precision decreases near underflow.
/// </remarks>
[Pure]
public static DoubleDouble Exp10(DoubleDouble value)
{
if (IsNaN(value))
{
return NaN;
}
// Loose guards include infinities and keep the exponent conversion bounded.
if (value.High > 309.0)
{
return PositiveInfinity;
}
if (value.High < -324.0)
{
return Zero;
}
if (IsZero(value))
{
return One;
}
// Independently split ln(2)/ln(10) at 180 and 260 decimal digits.
const double log10Of2Tail = 5.471948402314639e-35;
int exponent = (int)Math.Round(value.High * Log2Of10.High);
DoubleDouble reduced = value - (new DoubleDouble(Log10Of2.High) * exponent);
reduced -= new DoubleDouble(Log10Of2.Low) * exponent;
reduced -= new DoubleDouble(log10Of2Tail) * exponent;
return PreciseMathHelper.ScalePowerOfTwo(ExpReduced(reduced * Ln10), exponent);
}
/// <summary>Returns e raised to the specified value, minus one.</summary>
/// <remarks>
/// Uses a direct series near zero rather than subtracting one from a rounded
/// exponential, preserving tiny results. Signed zeros are preserved; negative
/// infinity returns negative one, positive infinity returns positive infinity,
/// and NaN returns canonical NaN. The tested approximate error bound is
/// 2^-100 relative to exp(value)-1 plus one minimum binary64 subnormal;
/// results are not guaranteed correctly rounded.
/// </remarks>
[Pure]
public static DoubleDouble ExpM1(DoubleDouble value)
{
if (IsZero(value))
{
return value;
}
if (Math.Abs(value.High) <= 0.5)
{
return ExpM1Small(value);
}
return Exp(value) - 1.0;
}
/// <summary>Returns two raised to the specified value, minus one.</summary>
/// <remarks>
/// Uses a cancellation-safe series near zero. Special values, signed zeros,
/// and approximate accuracy follow <see cref="ExpM1"/>, with the error bound
/// relative to 2^value-1. Other inputs use the base-two range reduction of
/// <see cref="Exp2"/> before subtracting one.
/// </remarks>
[Pure]
public static DoubleDouble Exp2M1(DoubleDouble value)
{
if (IsZero(value))
{
return value;
}
if (Math.Abs(value.High) <= 0.5)
{
return ExpM1Small(value * Ln2);
}
return Exp2(value) - 1.0;
}
/// <summary>Returns ten raised to the specified value, minus one.</summary>
/// <remarks>
/// Uses a cancellation-safe series near zero. Special values, signed zeros,
/// and approximate accuracy follow <see cref="ExpM1"/>, with the error bound
/// relative to 10^value-1. Other inputs use the base-ten range reduction of
/// <see cref="Exp10"/> before subtracting one.
/// </remarks>
[Pure]
public static DoubleDouble Exp10M1(DoubleDouble value)
{
if (IsZero(value))
{
return value;
}
if (Math.Abs(value.High) <= 0.125)
{
return ExpM1Small(value * Ln10);
}
return Exp10(value) - 1.0;
}
// Requires a finite normalized argument with |value| <= 0.5 plus rounding.
private static DoubleDouble ExpM1Small(DoubleDouble value)
{
if (Math.Abs(value.High) <= Math.ScaleB(1.0, -54))
{
// expm1(x) = x + x²/2 + O(x³): omitted relative error < 2^-110.
// Retain x even when its square underflows; never halve x first.
return value + ((value * value) * 0.5);
}
DoubleDouble term = value;
DoubleDouble sum = value;
for (int denominator = 2; denominator <= 32; ++denominator)
{
term = (term * value) / denominator;
sum += term;
}
// Relative truncation error <= 2*(0.5)^32/33! < 5.4e-47;
// double-double rounding dominates, including for negative arguments.
return sum;
}
// Requires a finite normalized argument with |reduced| < 0.347.
private static DoubleDouble ExpReduced(DoubleDouble reduced)
{
// P(x)/P(-x), with exact binary64 integer coefficients. For n=12,
// c_k = (24-k)!*12! / (24!*k!*(12-k)!), scaled by 1/c_12.
// This retains the legacy approximation but not its unsafe E^k scaling.
// Horner accumulators remain normal and finite on the reduced interval.
ReadOnlySpan<double> coefficients = [156.0, 12012.0, 600600.0, 21621600.0,
588107520.0, 12350257920.0, 201132771840.0, 2514159648000.0,
23465490048000.0, 154872234316800.0, 647647525324800.0, 1295295050649600.0];
DoubleDouble numerator = DoubleDouble.One;
DoubleDouble denominator = DoubleDouble.One;
foreach (double coefficient in coefficients)
{
numerator = (numerator * reduced) + coefficient;
denominator = (denominator * -reduced) + coefficient;
}
return numerator / denominator;
}
}
@@ -212,6 +212,31 @@ internal static class PreciseMathHelper
return ArithmeticFromRatio(ArithmeticUnits(left), ArithmeticUnits(right)); return ArithmeticFromRatio(ArithmeticUnits(left), ArithmeticUnits(right));
} }
// For finite, nonzero normalized significands and exponents bounded by the
// integer-power domain (|exponent| < 2^42). Keep the exponent separate until
// the final result so an intermediate cannot overflow before reciprocation.
internal static DoubleDouble ScalePowerOfTwo(DoubleDouble value, long exponent)
{
long resultExponent = Math.ILogB(value.High) + exponent;
if (resultExponent > 1024)
{
return new DoubleDouble(Math.CopySign(double.PositiveInfinity, value.High));
}
if (resultExponent < -1075)
{
return new DoubleDouble(Math.CopySign(0.0, value.High));
}
if (resultExponent >= -969 && resultExponent <= 1022)
{
// Normal high and room for a dense low; a sparse low may underflow
// by at most half a minimum subnormal. Normalize its signed zero.
return DoubleDouble.FromComponents(Math.ScaleB(value.High, (int)exponent),
Math.ScaleB(value.Low, (int)exponent));
}
return ScalePowerOfTwoBoundary(value, exponent);
}
// The caller supplies a finite, nonzero normalized value and has bounded // The caller supplies a finite, nonzero normalized value and has bounded
// ILogB(value.High) + exponent to [-1075, 1024], keeping both shifts small. // ILogB(value.High) + exponent to [-1075, 1024], keeping both shifts small.
// Isolate all allocating setup from ordinary scaling, including conversion // Isolate all allocating setup from ordinary scaling, including conversion
@@ -0,0 +1,227 @@
using System.Globalization;
using System.Numerics;
using Just.PreciseMath.Tests.ReferenceData;
using Shouldly;
using Xunit;
namespace Just.PreciseMath.Tests;
public class DoubleDoubleExponentialFunctionsTests
{
public static IEnumerable<TheoryDataRow<string, double, double, string, bool, bool>> ReferenceCases =>
ExponentialFunctionsReferenceData.Cases();
[Theory]
[MemberData(nameof(ReferenceCases))]
public void FiniteInputsMeetIndependentReferenceBound(string operation, double high, double low,
string reference, bool overflow, bool underflow)
{
// Exact binary64 sums and Decimal ln/exp/expm1 at 450/650 digits;
// integer base-two/base-ten powers are independently rational-checked.
DoubleDouble input = DoubleDouble.FromComponents(high, low);
(Units(input.High) + Units(input.Low)).ShouldBe(Units(high) + Units(low));
DoubleDouble actual = Evaluate(operation, input);
DoubleDouble.IsCanonical(actual).ShouldBeTrue();
AssertBits(actual, EvaluateGeneric(operation, input));
AssertBits(actual, EvaluateFacade(operation, input));
if (overflow)
{
AssertBits(DoubleDouble.PositiveInfinity, actual);
return;
}
if (underflow)
{
AssertBits(new DoubleDouble(reference[0] == '-' ? -0.0 : 0.0), actual);
return;
}
DoubleDouble.IsFinite(actual).ShouldBeTrue();
DoubleDouble.IsZero(actual).ShouldBeFalse();
string[] parts = reference.Split('e');
int point = parts[0].IndexOf('.', StringComparison.Ordinal);
int decimals = point < 0 ? 0 : parts[0].Length - point - 1;
BigInteger numerator = BigInteger.Parse(parts[0].Replace(".", "", StringComparison.Ordinal), CultureInfo.InvariantCulture);
int exponent = int.Parse(parts[1], CultureInfo.InvariantCulture) - decimals;
BigInteger denominator = BigInteger.One;
if (exponent >= 0)
{
numerator *= BigInteger.Pow(10, exponent);
}
else
{
denominator = BigInteger.Pow(10, -exponent);
}
BigInteger actualUnits = Units(actual.High) + Units(actual.Low);
BigInteger error = BigInteger.Abs((actualUnits * denominator) - (numerator << 1074));
BigInteger magnitude = BigInteger.Abs(numerator);
// 2^-100 relative + epsilon absolute + 2^-350 relative reference allowance.
BigInteger bound = (magnitude << 1324) + (denominator << 350) + (magnitude << 1074);
((error << 350) <= bound).ShouldBeTrue(
$"{operation} bound failed for ({high:R}, {low:R}): ({actual.High:R}, {actual.Low:R})");
}
[Fact]
public void NaturalExponentialIsAvailableOnTheTypeAndMatchesFacadeBits()
{
DoubleDouble[] values = [DoubleDouble.Zero, DoubleDouble.NegativeZero,
DoubleDouble.NaN, DoubleDouble.PositiveInfinity, DoubleDouble.NegativeInfinity,
new(-1.0), new(double.Epsilon), new(1.0), new(709.5), new(-740.0),
new(double.MaxValue), DoubleDouble.FromComponents(1.0, Math.ScaleB(1.0, -1000))];
foreach (DoubleDouble value in values)
{
AssertBits(DDMath.Exp(value), DoubleDouble.Exp(value));
}
}
[Fact]
public void BinaryAndDecimalExponentialsHaveExactElementaryValues()
{
for (int exponent = -1074; exponent <= 1023; ++exponent)
{
AssertBits(new DoubleDouble(Math.ScaleB(1.0, exponent)), DoubleDouble.Exp2(new DoubleDouble(exponent)));
}
AssertBits(DoubleDouble.Zero, DoubleDouble.Exp2(new DoubleDouble(-1075.0)));
AssertBits(DoubleDouble.Epsilon, DoubleDouble.Exp2(DoubleDouble.FromComponents(-1075.0, double.Epsilon)));
AssertBits(DoubleDouble.Zero, DoubleDouble.Exp2(DoubleDouble.FromComponents(-1075.0, -double.Epsilon)));
AssertBits(DoubleDouble.PositiveInfinity, DoubleDouble.Exp2(new DoubleDouble(1024.0)));
DoubleDouble ten = DoubleDouble.Exp10(DoubleDouble.One);
ten.High.ShouldBe(10.0);
Math.Abs(ten.Low).ShouldBeLessThan(1e-30);
AssertBits(DoubleDouble.One, DoubleDouble.Exp10(DoubleDouble.Zero));
}
[Fact]
public void MinusOneFunctionsRetainTinyResultsAndSignedZeros()
{
double tiny = Math.ScaleB(1.0, -100);
// exp(±x)-1 = ±x + x²/2 + O(x³). At x=2^-100 the tail is
// below half an ulp of the low, so these particular splits are exact.
AssertBits(DoubleDouble.FromComponents(tiny, Math.ScaleB(1.0, -201)), DoubleDouble.ExpM1(new DoubleDouble(tiny)));
AssertBits(DoubleDouble.FromComponents(-tiny, Math.ScaleB(1.0, -201)), DoubleDouble.ExpM1(new DoubleDouble(-tiny)));
foreach (double sign in new double[] { -1.0, 1.0 })
{
DoubleDouble input = new(sign * double.Epsilon);
AssertBits(input, DoubleDouble.ExpM1(input));
AssertBits(input, DoubleDouble.Exp2M1(input));
AssertBits(new DoubleDouble(sign * (2.0 * double.Epsilon)), DoubleDouble.Exp10M1(input));
}
}
[Theory]
[InlineData(1, double.Epsilon)]
[InlineData(500, 1.1210060331144859e-173)]
[InlineData(1000, 3.6694906201918696e-23)]
public void BinaryExponentialScalesSparseCorrectionsBeforeRounding(int exponent, double expectedLow)
{
// For delta=±2^-1074, 2^(n+delta)=2^n+delta*ln(2)*2^n+O(2^(n-2148)).
// Decimal ln(2)*2^(n-1074) at 450/650 digits gives the literals above.
// The quadratic term is far below their binary64 rounding intervals.
foreach (double sign in new double[] { -1.0, 1.0 })
{
DoubleDouble input = DoubleDouble.FromComponents(exponent, sign * double.Epsilon);
DoubleDouble expected = DoubleDouble.FromComponents(Math.ScaleB(1.0, exponent), sign * expectedLow);
AssertBits(expected, DoubleDouble.Exp2(input));
AssertBits(expected, DDMath.Exp2(input));
}
}
[Theory]
[InlineData("Exp")]
[InlineData("Exp2")]
[InlineData("Exp10")]
[InlineData("ExpM1")]
[InlineData("Exp2M1")]
[InlineData("Exp10M1")]
public void CompleteContractHasGenericDispatchAndThinFacade(string operation)
{
bool minusOne = operation.EndsWith("M1", StringComparison.Ordinal);
DoubleDouble[] values = [DoubleDouble.Zero, DoubleDouble.NegativeZero,
DoubleDouble.NaN, DoubleDouble.PositiveInfinity, DoubleDouble.NegativeInfinity,
new(double.MaxValue), new(double.MinValue), new(double.Epsilon),
new(0.125), new(-0.125), new(1.0), new(-1.0),
DoubleDouble.FromComponents(1.0, Math.ScaleB(1.0, -1000))];
foreach (DoubleDouble value in values)
{
DoubleDouble actual = Evaluate(operation, value);
DoubleDouble.IsCanonical(actual).ShouldBeTrue();
AssertBits(actual, EvaluateGeneric(operation, value));
AssertBits(actual, EvaluateFacade(operation, value));
if (DoubleDouble.IsZero(value))
{
AssertBits(minusOne ? value : DoubleDouble.One, actual);
}
else if (DoubleDouble.IsNaN(value))
{
AssertBits(DoubleDouble.NaN, actual);
}
else if (value.High == double.PositiveInfinity || value.High == double.MaxValue)
{
AssertBits(DoubleDouble.PositiveInfinity, actual);
}
else if (value.High == double.NegativeInfinity || value.High == double.MinValue)
{
AssertBits(minusOne ? DoubleDouble.NegativeOne : DoubleDouble.Zero, actual);
}
}
}
private static DoubleDouble Evaluate(string operation, DoubleDouble value)
{
return operation switch
{
"Exp" => DoubleDouble.Exp(value),
"Exp2" => DoubleDouble.Exp2(value),
"Exp10" => DoubleDouble.Exp10(value),
"ExpM1" => DoubleDouble.ExpM1(value),
"Exp2M1" => DoubleDouble.Exp2M1(value),
"Exp10M1" => DoubleDouble.Exp10M1(value),
_ => throw new ArgumentOutOfRangeException(nameof(operation))
};
}
private static T EvaluateGeneric<T>(string operation, T value) where T : IExponentialFunctions<T>
{
return operation switch
{
"Exp" => T.Exp(value),
"Exp2" => T.Exp2(value),
"Exp10" => T.Exp10(value),
"ExpM1" => T.ExpM1(value),
"Exp2M1" => T.Exp2M1(value),
"Exp10M1" => T.Exp10M1(value),
_ => throw new ArgumentOutOfRangeException(nameof(operation))
};
}
private static DoubleDouble EvaluateFacade(string operation, DoubleDouble value)
{
return operation switch
{
"Exp" => DDMath.Exp(value),
"Exp2" => DDMath.Exp2(value),
"Exp10" => DDMath.Exp10(value),
"ExpM1" => DDMath.ExpM1(value),
"Exp2M1" => DDMath.Exp2M1(value),
"Exp10M1" => DDMath.Exp10M1(value),
_ => throw new ArgumentOutOfRangeException(nameof(operation))
};
}
private static BigInteger Units(double value)
{
long bits = BitConverter.DoubleToInt64Bits(value);
int exponent = (int)((bits >> 52) & 0x7ff);
BigInteger significand = bits & 0xfffffffffffffL;
if (exponent != 0)
{
significand += BigInteger.One << 52;
significand <<= exponent - 1;
}
return bits < 0 ? -significand : significand;
}
private static void AssertBits(DoubleDouble expected, DoubleDouble actual)
{
BitConverter.DoubleToInt64Bits(actual.High).ShouldBe(BitConverter.DoubleToInt64Bits(expected.High));
BitConverter.DoubleToInt64Bits(actual.Low).ShouldBe(BitConverter.DoubleToInt64Bits(expected.Low));
}
}
@@ -0,0 +1,427 @@
// Generated by generate_exponential_functions.py; do not hand-edit.
// Exact binary64 sums verified with Fraction at 2200 digits; Decimal ln/exp and
// cancellation-safe expm1 at 450/650 digits; 120-digit references agree.
// Integer base-2/base-10 cases additionally use exact rational powers.
// Row: operation, high, low, reference, overflow, underflow.
// Overflow: exact result >= 2^1024 - 2^970 (binary64 overflow midpoint).
// Underflow: |exact result| <= 2^-1075, NOT exp(x) <= 2^-1075 for M1.
// Compare exact component sums with relative tolerance 2^-100 + double.Epsilon
// absolute; reference rounding uncertainty is bounded by 2^-350 relative.
// Tiny corrections to 1/-1 below 120 digits are not component-retention oracles.
// Signed zeros, infinities, NaNs, and enormous arguments are tested separately.
using Xunit;
namespace Just.PreciseMath.Tests.ReferenceData;
internal static class ExponentialFunctionsReferenceData
{
internal static IEnumerable<TheoryDataRow<string, double, double, string, bool, bool>> Cases()
{
// Exp2: ordinary
yield return new("Exp2", -10.25, 0.0, "8.21187905521205608428833472883998920937533459332797373841041099584887456009670156068675785350871617356251536858905356524e-4", false, false);
yield return new("Exp2", -2.0, 0.0, "2.5e-1", false, false);
yield return new("Exp2", -1.0, 0.0, "5e-1", false, false);
yield return new("Exp2", -0.125, 0.0, "9.17004043204671231743541594794144428038655166436839749791662069353238831122347362841465489380660313672341059668949459679e-1", false, false);
yield return new("Exp2", 0.125, 0.0, "1.09050773266525765920701065576070797899270271854006712178566764768330053084884184033821114049420311989145161926291809001e+0", false, false);
yield return new("Exp2", 0.75, 0.0, "1.68179283050742908606225095246642979008006852471356902162645217194984950990780447962864800839858507234560314748703817016e+0", false, false);
yield return new("Exp2", 1.0, 0.0, "2e+0", false, false);
yield return new("Exp2", 3.25, 0.0, "9.51365692002176853373999976448380732234377673971053930415201779775573334581533727896625075630510138972829915819815370549e+0", false, false);
// Exp2: dense-and-sparse-low
yield return new("Exp2", -1.0, 5.551115123125783e-17, "5.00000000000000019238698982791550072604160854284538415449960340473288044078594786193838693052953287736809338580638344518e-1", false, false);
yield return new("Exp2", -1.0, -5.551115123125783e-17, "4.99999999999999980761301017208450667650916246644680054760727103703961478639310202113257847265570935275551812571858143798e-1", false, false);
yield return new("Exp2", -1.0, 5e-324, "5.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e-1", false, false);
yield return new("Exp2", -1.0, -5e-324, "5.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e-1", false, false);
yield return new("Exp2", 1.0, 5.551115123125783e-17, "2.00000000000000007695479593116620029041664341713815366179984136189315217631437914477535477221181315094723735432255337807e+0", false, false);
yield return new("Exp2", 1.0, -5.551115123125783e-17, "1.99999999999999992304520406883380267060366498657872021904290841481584591455724080845303138906228374110220725028743257519e+0", false, false);
yield return new("Exp2", 1.0, 5e-324, "2.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp2", 1.0, -5e-324, "2.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
// Exp2: tiny
yield return new("Exp2", -1e-20, 0.0, "9.99999999999999999993068528194400547286020253980627191054756284100556423022379868463187217573182659816366797509203417810e-1", false, false);
yield return new("Exp2", 1e-20, 0.0, "1.00000000000000000000693147180559945271402779132076462908244017094540055080820895837340445945043791570869517416287708474e+0", false, false);
yield return new("Exp2", -1e-100, 0.0, "9.99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999930685281944005467673e-1", false, false);
yield return new("Exp2", 1e-100, 0.0, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006931471805599453233e+0", false, false);
yield return new("Exp2", -1e-300, 0.0, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp2", 1e-300, 0.0, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp2", -2.2250738585072014e-308, 0.0, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp2", 2.2250738585072014e-308, 0.0, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp2", -1e-323, 0.0, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp2", 1e-323, 0.0, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp2", -5e-324, 0.0, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp2", 5e-324, 0.0, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
// Exp2: absolute-log-half-switch
yield return new("Exp2", -0.7213475204444817, -1.0177636870465518e-17, "6.06530659712633423603799534991179582561900207458797347807571359489015133003532873390753100210381268501490009310808759694e-1", false, false);
yield return new("Exp2", -0.7213475204444817, -1.0177636870465517e-17, "6.06530659712633423603799534991180230313795998115094343092035618278013018112551514441716068365329629038114330858826160472e-1", false, false);
yield return new("Exp2", -0.7213475204444817, -1.0177636870465515e-17, "6.06530659712633423603799534991180878065691788771391338376499877067702677844635677291755121257605846666377380013169852043e-1", false, false);
yield return new("Exp2", 0.7213475204444817, 1.0177636870465515e-17, "1.64872127070012814684865078781416241740668824728100260241580595072860002906738406941721084254203061497902616864239766825e+0", false, false);
yield return new("Exp2", 0.7213475204444817, 1.0177636870465517e-17, "1.64872127070012814684865078781416417817889592491912837676279009285057271715417266380529283718974446456952552825625487127e+0", false, false);
yield return new("Exp2", 0.7213475204444817, 1.0177636870465518e-17, "1.64872127070012814684865078781416593895110360255725415110977423497442584362822937159780260677266950493781041819896683958e+0", false, false);
// Exp2: sparse-correction-switch
yield return new("Exp2", 1000.0, -3.0549363634996054e-151, "1.07150860718626732094842504906000181056140481170553360744375038837035105112493612249319837881569585812759467291755314683e+301", false, false);
yield return new("Exp2", 1000.0, -3.054936363499605e-151, "1.07150860718626732094842504906000181056140481170553360744375038837035105112493612249319837881569585812759467291755314683e+301", false, false);
yield return new("Exp2", 1000.0, -3.0549363634996043e-151, "1.07150860718626732094842504906000181056140481170553360744375038837035105112493612249319837881569585812759467291755314683e+301", false, false);
yield return new("Exp2", 1000.0, 3.0549363634996043e-151, "1.07150860718626732094842504906000181056140481170553360744375038837035105112493612249319837881569585812759467291755314683e+301", false, false);
yield return new("Exp2", 1000.0, 3.054936363499605e-151, "1.07150860718626732094842504906000181056140481170553360744375038837035105112493612249319837881569585812759467291755314683e+301", false, false);
yield return new("Exp2", 1000.0, 3.0549363634996054e-151, "1.07150860718626732094842504906000181056140481170553360744375038837035105112493612249319837881569585812759467291755314683e+301", false, false);
// Exp2: random-moderate
yield return new("Exp2", 8.78452800194328, -4.440892098500626e-16, "4.40967341476995196084861795619177036986833446346865068997727335172090686266840323292806805468322290875886544488139632805e+2", false, false);
yield return new("Exp2", 2.7202982684631807, 1.1102230246251565e-16, "6.59009045860470968459866968889737028605350979742874890837108301124392819835145507472654892784605100090392179416858372658e+0", false, false);
yield return new("Exp2", -3.4144589655515833, 1.1102230246251565e-16, "9.37876021346977504985476134528503635574385608741052067745862593845534384960660409900825318502561533036509957790741090194e-2", false, false);
yield return new("Exp2", 3.67643978981615, 1.1102230246251565e-16, "1.27855275975436677510222614327660438420381423323350572823327975657260641462810932407195792018571153800381071193261125222e+1", false, false);
yield return new("Exp2", 7.559099749783131, -2.220446049250313e-16, "1.88588742600978100098560391503204728439903232936850014376858946537478147694222110588671754338725081209043377353414161445e+2", false, false);
yield return new("Exp2", 1.1272538511782404, 5.551115123125783e-17, "2.18442542093619461179505632322739881400577647657120007022725830276626252983657181904591357904539459020575218758691298325e+0", false, false);
// Exp2: random-range
yield return new("Exp2", -816.2214079897565, -2.842170943040401e-14, "1.96278663442385783610897157419095408652661650100352671243288814392820295492839788615258722894068315168311070489220014454e-246", false, false);
yield return new("Exp2", -678.2241907878085, -2.842170943040401e-14, "6.82613366240855811298682466187049063105090237181643243484884807542082161846280596239346667442720742223177409347157282675e-205", false, false);
yield return new("Exp2", -370.71699147959754, -1.4210854715202004e-14, "2.52968043778232094121601606971876522062602966535407014705556941483082015232179403400791844339483992427445921351569246286e-112", false, false);
yield return new("Exp2", 325.3049427158436, 1.4210854715202004e-14, "8.44394693210093550736509533332028359812000552799434020131118049620518568591929165240041457560841465860244163260586337592e+97", false, false);
yield return new("Exp2", 956.9782395104039, 2.842170943040401e-14, "1.19992828387600858106093631065960712137870691174075367927710136390487281510229839467771780838917585057717098231260902141e+288", false, false);
yield return new("Exp2", 364.3581585496251, -1.4210854715202004e-14, "4.81653686139180239709139190167158242071136853423964187582462482485565537885773294376979018541476826443148951897942014775e+109", false, false);
// Exp2: integer-power
yield return new("Exp2", -1076.0, 0.0, "1.23516411460311636044142198217055343091264950653581191106396420625168876817552187966324959090408998094949141173861429433e-324", false, true);
yield return new("Exp2", -1075.0, 0.0, "2.47032822920623272088284396434110686182529901307162382212792841250337753635104375932649918180817996189898282347722858865e-324", false, true);
yield return new("Exp2", -1074.0, 0.0, "4.94065645841246544176568792868221372365059802614324764425585682500675507270208751865299836361635992379796564695445717731e-324", false, false);
yield return new("Exp2", -1022.0, 0.0, "2.22507385850720138309023271733240406421921598046233183055332741688720443481391819585428315901251102056406733973103581101e-308", false, false);
yield return new("Exp2", -100.0, 0.0, "7.888609052210118054117285652827862296732064351090230047702789306640625e-31", false, false);
yield return new("Exp2", 10.0, 0.0, "1.024e+3", false, false);
yield return new("Exp2", 53.0, 0.0, "9.007199254740992e+15", false, false);
yield return new("Exp2", 100.0, 0.0, "1.267650600228229401496703205376e+30", false, false);
yield return new("Exp2", 1023.0, 0.0, "8.98846567431157953864652595394512366808988489471153286367150405788663379027504815663542386612037680105600569399356966788e+307", false, false);
yield return new("Exp2", 1024.0, 0.0, "1.79769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393358e+308", true, false);
// Exp2: overflow-adjacent-low
yield return new("Exp2", 1024.0, -8.008566259537295e-17, "1.79769313486231580793728971405302296962136452782486920563609826833849467898852832283391964137375521264887063753644165960e+308", false, false);
yield return new("Exp2", 1024.0, -8.008566259537294e-17, "1.79769313486231580793728971405303832856973767181831156142887561931500193969224300858151352073314340505642260157691456128e+308", true, false);
yield return new("Exp2", 1024.0, -8.008566259537293e-17, "1.79769313486231580793728971405305368751811081581175391722165297042273142656692749614371314702169582876170167447259768366e+308", true, false);
// Exp2: underflow-adjacent-low
yield return new("Exp2", -1075.0, -5e-324, "2.47032822920623272088284396434110686182529901307162382212792841250337753635104375932649918180817996189898282347722858865e-324", false, true);
yield return new("Exp2", -1075.0, 5e-324, "2.47032822920623272088284396434110686182529901307162382212792841250337753635104375932649918180817996189898282347722858865e-324", false, false);
// Exp2: min-normal-adjacent-low
yield return new("Exp2", -1022.0, -5e-324, "2.22507385850720138309023271733240406421921598046233183055332741688720443481391819585428315901251102056406733973103581101e-308", false, false);
yield return new("Exp2", -1022.0, 5e-324, "2.22507385850720138309023271733240406421921598046233183055332741688720443481391819585428315901251102056406733973103581101e-308", false, false);
// Exp10: ordinary
yield return new("Exp10", -10.25, 0.0, "5.62341325190349080394951039776481231468251043098691664081689423735883568643062848905857984526220305928676107320100325218e-11", false, false);
yield return new("Exp10", -2.0, 0.0, "1e-2", false, false);
yield return new("Exp10", -1.0, 0.0, "1e-1", false, false);
yield return new("Exp10", -0.125, 0.0, "7.49894209332455827302184275615136438441867918164971014620419005429827525167160627980673695983144556246592084007724058545e-1", false, false);
yield return new("Exp10", 0.125, 0.0, "1.33352143216332402567593171529533109241566796476437099332954998716275894318019581864901349800473255887744566135767837809e+0", false, false);
yield return new("Exp10", 0.75, 0.0, "5.62341325190349080394951039776481231468251043098691664081689423735883568643062848905857984526220305928676107320100325218e+0", false, false);
yield return new("Exp10", 1.0, 0.0, "1.0e+1", false, false);
yield return new("Exp10", 3.25, 0.0, "1.77827941003892280122542119519268484473579052640225535801183072277630188153949380490030039927870215508827904815953580779e+3", false, false);
// Exp10: dense-and-sparse-low
yield return new("Exp10", -1.0, 5.551115123125783e-17, "1.00000000000000012781914932003234537245680382409083392670440261696902368520426109154220545198530672142968125154509195453e-1", false, false);
yield return new("Exp10", -1.0, -5.551115123125783e-17, "9.99999999999999872180850679967670965278129072632256181650151054098060763085821567736360614288865296549471193611253084211e-2", false, false);
yield return new("Exp10", -1.0, 5e-324, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e-1", false, false);
yield return new("Exp10", -1.0, -5e-324, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e-1", false, false);
yield return new("Exp10", 1.0, 5.551115123125783e-17, "1.00000000000000012781914932003234537245680382409083392670440261696902368520426109154220545198530672142968125154509195453e+1", false, false);
yield return new("Exp10", 1.0, -5.551115123125783e-17, "9.99999999999999872180850679967670965278129072632256181650151054098060763085821567736360614288865296549471193611253084211e+0", false, false);
yield return new("Exp10", 1.0, 5e-324, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+1", false, false);
yield return new("Exp10", 1.0, -5e-324, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+1", false, false);
// Exp10: tiny
yield return new("Exp10", -1e-20, 0.0, "9.99999999999999999976974149070059544422977775848960612440147585677561893017435981776880676797244875417274786359562174097e-1", false, false);
yield return new("Exp10", 1e-20, 0.0, "1.00000000000000000002302585092994045557755241396208722730275022767272793307903996143344756701626889076812422686037317339e+0", false, false);
yield return new("Exp10", -1e-100, 0.0, "9.99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999769741490700595426995e-1", false, false);
yield return new("Exp10", 1e-100, 0.0, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023025850929940457301e+0", false, false);
yield return new("Exp10", -1e-300, 0.0, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp10", 1e-300, 0.0, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp10", -2.2250738585072014e-308, 0.0, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp10", 2.2250738585072014e-308, 0.0, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp10", -1e-323, 0.0, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp10", 1e-323, 0.0, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp10", -5e-324, 0.0, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp10", 5e-324, 0.0, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
// Exp10: absolute-log-half-switch
yield return new("Exp10", -0.2171472409516259, -5.491598251083826e-18, "6.06530659712633423603799534991179637117927421578992542354041909882833935611531036427818598568463751669923025111585399002e-1", false, false);
yield return new("Exp10", -0.2171472409516259, -5.491598251083825e-18, "6.06530659712633423603799534991180713010537993345061928799291965018127819688823024941030203305141921320141807673626493593e-1", false, false);
yield return new("Exp10", -0.2171472409516259, -5.4915982510838246e-18, "6.06530659712633423603799534991181788903148565111131315244542020155330172680170174417944401325366675980091613028373016444e-1", false, false);
yield return new("Exp10", 0.2171472409516259, 5.4915982510838246e-18, "1.64872127070012814684865078781415994149378081230563234818972414648475614387142248427209749759453651737942752570661371215e+0", false, false);
yield return new("Exp10", 0.2171472409516259, 5.491598251083825e-18, "1.64872127070012814684865078781416286607311350290140527066142468471602253792023469276276331171381066284701617511531416884e+0", false, false);
yield return new("Exp10", 0.2171472409516259, 5.491598251083826e-18, "1.64872127070012814684865078781416579065244619349717819313312522295247668833830201265250709635897011641393592320394302761e+0", false, false);
// Exp10: random-moderate
yield return new("Exp10", -2.9677357165710085, -1.1102230246251565e-16, "1.07712047983496528579184014784924244267541657758050199455170643282791807045975507835023158082182189438408871295653951051e-3", false, false);
yield return new("Exp10", 0.5995899657426765, -2.7755575615628914e-17, "3.97731479519890352879887600772233924298609686175885392875139750363632580171965156242522363738652933349600506618119299140e+0", false, false);
yield return new("Exp10", 0.8836851964549527, -2.7755575615628914e-17, "7.65041856144971858829962647061748349456294701097773926744045197071139270643977619590118903694461196808465346045233138263e+0", false, false);
yield return new("Exp10", -0.89395009436352, -2.7755575615628914e-17, "1.27658549534319782190947058351173152520381158276033885330906540617729206451590520184715981551175953612070355802078326647e-1", false, false);
yield return new("Exp10", 2.4580147142578235, -1.1102230246251565e-16, "2.87087784810333896489677828661765156448804286447564220961070487382944745109858177125943722517534602403829765495652338124e+2", false, false);
yield return new("Exp10", 1.3118516264245281, 5.551115123125783e-17, "2.05046153376223160193906750616367118569485139159211538092010333804564633619337531743249262303443651659625057726925490890e+1", false, false);
// Exp10: random-range
yield return new("Exp10", 120.58969150830279, 3.552713678800501e-15, "3.88768893311729066468034240903516540867755441511370722931132770507875572390376800025883066808459259868657675470129694955e+120", false, false);
yield return new("Exp10", 128.81118142762747, -7.105427357601002e-15, "6.47413017703446171983916778641885806651744797264667468232608054450205762476305583291383873034783101822647556402661401770e+128", false, false);
yield return new("Exp10", 42.94992135127048, 1.7763568394002505e-15, "8.91089551309232685378712887186962248614334767700386757268709525041370952477415725129562497023716824045224543686275322633e+42", false, false);
yield return new("Exp10", 116.81408221381518, -3.552713678800501e-15, "6.51751761797928744146122574992738336073782813915351110967844224366907044327161884077729178445278842875234452038465777427e+116", false, false);
yield return new("Exp10", 228.96449953361966, -7.105427357601002e-15, "9.21508899144045996730372464171725339874292277767159122312745668742022577561608598769932754185213295353703683516203829072e+228", false, false);
yield return new("Exp10", 7.524609271856807, 2.220446049250313e-16, "3.34664211369735369097716632117090216287459757705675042241703792408934408612576386525573391905086740939515500212837287795e+7", false, false);
// Exp10: integer-power
yield return new("Exp10", -324.0, 0.0, "1e-324", false, true);
yield return new("Exp10", -323.0, 0.0, "1e-323", false, false);
yield return new("Exp10", -308.0, 0.0, "1e-308", false, false);
yield return new("Exp10", 22.0, 0.0, "1.0000000000000000000000e+22", false, false);
yield return new("Exp10", 308.0, 0.0, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+308", false, false);
yield return new("Exp10", 309.0, 0.0, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+309", true, false);
// Exp10: overflow-adjacent-low
yield return new("Exp10", 308.25471555991675, -2.895125789515847e-15, "1.79769313486231580793728971405135339792924520420229515147549215774814264965213204794170642362509327672779802464860968196e+308", false, false);
yield return new("Exp10", 308.25471555991675, -2.8951257895158466e-15, "1.79769313486231580793728971405298608023672269474350236922097481966929437654805569985823689453925527448616186188699371312e+308", false, false);
yield return new("Exp10", 308.25471555991675, -2.8951257895158462e-15, "1.79769313486231580793728971405461876254420018528470958696645896440832277858828718822634729443205898882595653371847623943e+308", true, false);
// Exp10: underflow-adjacent-low
yield return new("Exp10", -323.60724533877976, -2.2576632980240802e-14, "2.47032822920623272088284396433158418397152352476997073314847977407861399577439129056325691274224699350470920929775150941e-324", false, true);
yield return new("Exp10", -323.60724533877976, -2.25766329802408e-14, "2.47032822920623272088284396434953279206384344178901504121903931494189587104807927555924014551585189156808716237545881368e-324", false, false);
yield return new("Exp10", -323.60724533877976, -2.2576632980240796e-14, "2.47032822920623272088284396436748140015616335880805934928972926460213150426298975260692893446611258989980884791171466090e-324", false, false);
// Exp10: min-normal-adjacent-low
yield return new("Exp10", -307.6526555685888, 2.754387844133928e-15, "2.22507385850720138309023271732978958155884829152404782449780586939390649146037065767628694530848808276307239212102756320e-308", false, false);
yield return new("Exp10", -307.6526555685888, 2.7543878441339283e-15, "2.22507385850720138309023271733181041517675806770793310094027818310578520394298980281484276867693115447731727042755624095e-308", false, false);
yield return new("Exp10", -307.6526555685888, 2.7543878441339287e-15, "2.22507385850720138309023271733383124879466784389181837738275233215831818760979011696161815647039528165171309747797451182e-308", false, false);
// ExpM1: ordinary
yield return new("ExpM1", -10.25, 0.0, "-9.99964642499149590017595412360236722574820646856009376883960968787652399508581301416230500941667060422476262375531037864e-1", false, false);
yield return new("ExpM1", -2.0, 0.0, "-8.64664716763387308106000505027515596592368454090424118531841127345926625898512310062901877509342951244922712810366447788e-1", false, false);
yield return new("ExpM1", -1.0, 0.0, "-6.32120558828557678404476229838539132554188868968232165492163198302538504255100196642852725654080356253372674723156004792e-1", false, false);
yield return new("ExpM1", -0.125, 0.0, "-1.17503097415404597135107856770949263777995175009349258229690680791890704788691269900644852670056561354676484984318222954e-1", false, false);
yield return new("ExpM1", 0.125, 0.0, "1.33148453066826316829007227811793872565503131745181625912820036078823577880048386513939990794941728573231527015647307566e-1", false, false);
yield return new("ExpM1", 0.75, 0.0, "1.11700001661267466854536981983709561013449158470240342177913303081098453336401282000279156026661579821888590471901551426e+0", false, false);
yield return new("ExpM1", 1.0, 0.0, "1.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642742746639193200305992e+0", false, false);
yield return new("ExpM1", 3.25, 0.0, "2.47903399171930620890801076693772218766552688489539736340424977271388720447207274888484147322397605262439439122840412454e+1", false, false);
// ExpM1: dense-and-sparse-low
yield return new("ExpM1", -1.0, 5.551115123125783e-17, "-6.32120558828557657983064936099986792792127784222576303251939251170289698165054910720245453994927423599213941197314427262e-1", false, false);
yield return new("ExpM1", -1.0, -5.551115123125783e-17, "-6.32120558828557698825887523577090338700199271276585542169177230003595532046796634595998012127496903342004079921719212416e-1", false, false);
yield return new("ExpM1", -1.0, 5e-324, "-6.32120558828557678404476229838539132554188868968232165492163198302538504255100196642852725654080356253372674723156004792e-1", false, false);
yield return new("ExpM1", -1.0, -5e-324, "-6.32120558828557678404476229838539132554188868968232165492163198302538504255100196642852725654080356253372674723156004792e-1", false, false);
yield return new("ExpM1", 1.0, 5.551115123125783e-17, "1.71828182845904538625524114012277294245207378041324564716578593432480201210931242665209562398991569452359995574369903890e+0", false, false);
yield return new("ExpM1", 1.0, -5.551115123125783e-17, "1.71828182845904508446533380258256042941501354772712009605959080847470919809942371990293379281005694591784351622350569198e+0", false, false);
yield return new("ExpM1", 1.0, 5e-324, "1.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642742746639193200305992e+0", false, false);
yield return new("ExpM1", 1.0, -5e-324, "1.71828182845904523536028747135266249775724709369995957496696762772407663035354759457138217852516642742746639193200305992e+0", false, false);
// ExpM1: tiny
yield return new("ExpM1", -1e-20, 0.0, "-9.99999999999999945148271454209571652277987654911963382213526866733506442994862496388124890915444431248853256190340604605e-21", false, false);
yield return new("ExpM1", 1e-20, 0.0, "9.99999999999999945158271454209571651181053083996154845329753345878813976563872245650392070698072643274472760230268014801e-21", false, false);
yield return new("ExpM1", -1e-100, 0.0, "-1.00000000000000001999189980260288361964776078853415942018260300593659569925554346761767628861329298953274607481091184880e-100", false, false);
yield return new("ExpM1", 1e-100, 0.0, "1.00000000000000001999189980260288361964776078853415942018260300593659569925554346761767628861329298963274607481091185280e-100", false, false);
yield return new("ExpM1", -1e-300, 0.0, "-1.00000000000000002505909183520875968569614680770370524992534231990046604318405148467630281218195010089496230627027825415e-300", false, false);
yield return new("ExpM1", 1e-300, 0.0, "1.00000000000000002505909183520875968569614680770370524992534231990046604318405148467630281218195010089496230627027825415e-300", false, false);
yield return new("ExpM1", -2.2250738585072014e-308, 0.0, "-2.22507385850720138309023271733240406421921598046233183055332741688720443481391819585428315901251102056406733973103581101e-308", false, false);
yield return new("ExpM1", 2.2250738585072014e-308, 0.0, "2.22507385850720138309023271733240406421921598046233183055332741688720443481391819585428315901251102056406733973103581101e-308", false, false);
yield return new("ExpM1", -1e-323, 0.0, "-9.88131291682493088353137585736442744730119605228649528851171365001351014540417503730599672723271984759593129390891435462e-324", false, false);
yield return new("ExpM1", 1e-323, 0.0, "9.88131291682493088353137585736442744730119605228649528851171365001351014540417503730599672723271984759593129390891435462e-324", false, false);
yield return new("ExpM1", -5e-324, 0.0, "-4.94065645841246544176568792868221372365059802614324764425585682500675507270208751865299836361635992379796564695445717731e-324", false, false);
yield return new("ExpM1", 5e-324, 0.0, "4.94065645841246544176568792868221372365059802614324764425585682500675507270208751865299836361635992379796564695445717731e-324", false, false);
// ExpM1: absolute-log-half-switch
yield return new("ExpM1", -0.5, -5e-324, "-3.93469340287366576396200465008819546558081864512813044317107841264943480586251576001352388492010543973576210205960474823e-1", false, false);
yield return new("ExpM1", -0.5, 0.0, "-3.93469340287366576396200465008819546558081864512813044317107841264943480586251576001352388492010543973576210205960474823e-1", false, false);
yield return new("ExpM1", -0.5, 5e-324, "-3.93469340287366576396200465008819546558081864512813044317107841264943480586251576001352388492010543973576210205960474823e-1", false, false);
yield return new("ExpM1", 0.5, -5e-324, "6.48721270700128146848650787814163571653776100710148011575079311640661021194215608632776520056366643002866637756307797005e-1", false, false);
yield return new("ExpM1", 0.5, 0.0, "6.48721270700128146848650787814163571653776100710148011575079311640661021194215608632776520056366643002866637756307797005e-1", false, false);
yield return new("ExpM1", 0.5, 5e-324, "6.48721270700128146848650787814163571653776100710148011575079311640661021194215608632776520056366643002866637756307797005e-1", false, false);
// ExpM1: input-high-series-switch
yield return new("ExpM1", -0.5000000000000001, 0.0, "-3.93469340287366643734630820413961752893910543944792323093955342455151126428014543140163447963889188936567569177072330420e-1", false, false);
yield return new("ExpM1", -0.49999999999999994, 0.0, "-3.93469340287366542726985287306245639864824173983524003239030957439240694784182493508171378245214271234180273050238838247e-1", false, false);
yield return new("ExpM1", -0.5, -2.7755575615628914e-17, "-3.93469340287366593230808053860105799023374872074113261068818573492447875650538514346733263349345635337465711156202652272e-1", false, false);
yield return new("ExpM1", -0.5, 2.7755575615628914e-17, "-3.93469340287366559561592876157532826838564965149287614677159619553512682806916609709611128939222384005591032211301092594e-1", false, false);
// ExpM1: quadratic-series-switch
yield return new("ExpM1", -5.551115123125783e-17, -5e-324, "-5.55111512312578254804376278947523616897078869154779750199182577185527323675307989703604544287548416109752829359640238674e-17", false, false);
yield return new("ExpM1", -5.551115123125783e-17, 0.0, "-5.55111512312578254804376278947523616897078869154779750199182577185527323675307989703604544287548416109752829359640238674e-17", false, false);
yield return new("ExpM1", -5.551115123125783e-17, 5e-324, "-5.55111512312578254804376278947523616897078869154779750199182577185527323675307989703604544287548416109752829359640238674e-17", false, false);
// ExpM1: input-high-series-switch
yield return new("ExpM1", 0.49999999999999994, 0.0, "6.48721270700128055326234991787778407132549255798122477493863918355113649772615017427283934825041206826640936896304389788e-1", false, false);
yield return new("ExpM1", 0.5000000000000001, 0.0, "6.48721270700128329893482379866949142240222700377506852855419426513165259729393218477113635478077785280838920026492973826e-1", false, false);
yield return new("ExpM1", 0.5, -2.7755575615628914e-17, "6.48721270700128101087442889800970354328829640344026798281419430748374005506759742356897159987521249118694442987495686887e-1", false, false);
yield return new("ExpM1", 0.5, 2.7755575615628914e-17, "6.48721270700128192609858685827358059107388636896521370527076406346895321998228741244042216578759957646849801680426913917e-1", false, false);
// ExpM1: quadratic-series-switch
yield return new("ExpM1", 5.551115123125783e-17, -5e-324, "5.55111512312578285619255389143297265792725950513624759781930415130694675216220020400320592376210494940133730810649727575e-17", false, false);
yield return new("ExpM1", 5.551115123125783e-17, 0.0, "5.55111512312578285619255389143297265792725950513624759781930415130694675216220020400320592376210494940133730810649727575e-17", false, false);
yield return new("ExpM1", 5.551115123125783e-17, 5e-324, "5.55111512312578285619255389143297265792725950513624759781930415130694675216220020400320592376210494940133730810649727575e-17", false, false);
// ExpM1: random-moderate
yield return new("ExpM1", 1.8665732301127385, 5.551115123125783e-17, "5.46610055231604164741021410411512794541063980685522084739073087713841387734128034155842973464987332959651169399776074928e+0", false, false);
yield return new("ExpM1", -3.836275153651057, -1.1102230246251565e-16, "-9.78426189000834536644301856044613215358744428231199467965735926574776462346821532341070704496171040555177164906750092116e-1", false, false);
yield return new("ExpM1", -7.926421267629822, 2.220446049250313e-16, "-9.99638923701085515458633712931479267616303538413149722750742992858886680422776234425246545391734902973472717556638544616e-1", false, false);
yield return new("ExpM1", 0.809076467549632, -2.7755575615628914e-17, "1.24583292904435095047625826229914076404174762338755219183658975528974302916271313844842172254318969775653936199802268813e+0", false, false);
yield return new("ExpM1", 2.395252849322306, 1.1102230246251565e-16, "9.97097171116982451435344657554439752704646874789848866365033063395867538506970643915392963325796240244200998541676010281e+0", false, false);
yield return new("ExpM1", 6.360537727726566, 2.220446049250313e-16, "5.77557379107619288839611291974544367870013144362286685855659151210210389821741943112835250334310901695746967316162020030e+2", false, false);
// ExpM1: random-range
yield return new("ExpM1", -717.471294689501, -2.842170943040401e-14, "-1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("ExpM1", -527.7157218253692, -2.842170943040401e-14, "-1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("ExpM1", -322.9587865258026, -1.4210854715202004e-14, "-1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("ExpM1", -24.19275074210509, -8.881784197001252e-16, "-9.99999999968866936988232338142807791500416465558403067189833971761249730793225364843678313704660239258534490554851101589e-1", false, false);
yield return new("ExpM1", -26.228830895869123, -8.881784197001252e-16, "-9.99999999995935908519211296503644770852506238245358308752490322883971257357333755851647526243130367097065447570813612841e-1", false, false);
yield return new("ExpM1", 407.0814289754503, 1.4210854715202004e-14, "6.21181179920291117447313962056204710364585787558821473816816695867214972444088839539628676739709899564766755487635174426e+176", false, false);
// ExpM1: negative-saturation
yield return new("ExpM1", -40.0, 0.0, "-9.99999999999999995751645744708411004670765217141341982120434445833553711949181081073966936073085345895610771405272219090e-1", false, false);
yield return new("ExpM1", -100.0, 0.0, "-9.99999999999999999999999999999999999999999962799240239791640370403041961368816626411077076232180328793861233367095241042e-1", false, false);
yield return new("ExpM1", -745.0, 0.0, "-1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("ExpM1", -746.0, 0.0, "-1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
// ExpM1: overflow-adjacent-low
yield return new("ExpM1", 709.782712893384, 2.3691528222554846e-14, "1.79769313486231580793728971404631370472204944707885473394255307689401631566863219557545655233948936948926513968471917798e+308", false, false);
yield return new("ExpM1", 709.782712893384, 2.369152822255485e-14, "1.79769313486231580793728971405198622405675778669468436196455240891719566132544008818951355194454679628152242602530089901e+308", false, false);
yield return new("ExpM1", 709.782712893384, 2.3691528222554853e-14, "1.79769313486231580793728971405765874339146612631051398998656964025532405383200713096445663417114297060553540946791444483e+308", true, false);
// Exp2M1: ordinary
yield return new("Exp2M1", -10.25, 0.0, "-9.99178812094478794391571166527116001079062466540667202626158958900415112543990329843931324214649128382643748463141094643e-1", false, false);
yield return new("Exp2M1", -2.0, 0.0, "-7.5e-1", false, false);
yield return new("Exp2M1", -1.0, 0.0, "-5e-1", false, false);
yield return new("Exp2M1", -0.125, 0.0, "-8.29959567953287682564584052058555719613448335631602502083379306467611688776526371585345106193396863276589403310505403211e-2", false, false);
yield return new("Exp2M1", 0.125, 0.0, "9.05077326652576592070106557607079789927027185400671217856676476833005308488418403382111404942031198914516192629180900103e-2", false, false);
yield return new("Exp2M1", 0.75, 0.0, "6.81792830507429086062250952466429790080068524713569021626452171949849509907804479628648008398585072345603147487038170160e-1", false, false);
yield return new("Exp2M1", 1.0, 0.0, "1e+0", false, false);
yield return new("Exp2M1", 3.25, 0.0, "8.51365692002176853373999976448380732234377673971053930415201779775573334581533727896625075630510138972829915819815370549e+0", false, false);
// Exp2M1: dense-and-sparse-low
yield return new("Exp2M1", -1.0, 5.551115123125783e-17, "-4.99999999999999980761301017208449927395839145715461584550039659526711955921405213806161306947046712263190661419361655482e-1", false, false);
yield return new("Exp2M1", -1.0, -5.551115123125783e-17, "-5.00000000000000019238698982791549332349083753355319945239272896296038521360689797886742152734429064724448187428141856202e-1", false, false);
yield return new("Exp2M1", -1.0, 5e-324, "-5.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e-1", false, false);
yield return new("Exp2M1", -1.0, -5e-324, "-5.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e-1", false, false);
yield return new("Exp2M1", 1.0, 5.551115123125783e-17, "1.00000000000000007695479593116620029041664341713815366179984136189315217631437914477535477221181315094723735432255337807e+0", false, false);
yield return new("Exp2M1", 1.0, -5.551115123125783e-17, "9.99999999999999923045204068833802670603664986578720219042908414815845914557240808453031389062283741102207250287432575192e-1", false, false);
yield return new("Exp2M1", 1.0, 5e-324, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp2M1", 1.0, -5e-324, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
// Exp2M1: tiny
yield return new("Exp2M1", -1e-20, 0.0, "-6.93147180559945271397974601937280894524371589944357697762013153681278242681734018363320249079658219031934314131211295962e-21", false, false);
yield return new("Exp2M1", 1e-20, 0.0, "6.93147180559945271402779132076462908244017094540055080820895837340445945043791570869517416287708473950733796857874762083e-21", false, false);
yield return new("Exp2M1", -1e-100, 0.0, "-6.93147180559945323274561103669292529709311557561290569992180233374923094914456585984237940738350279587910585083533591459e-101", false, false);
yield return new("Exp2M1", 1e-100, 0.0, "6.93147180559945323274561103669292529709311557561290569992180233374923094914456585984237940738350279635955886475353735846e-101", false, false);
yield return new("Exp2M1", -1e-300, 0.0, "-6.93147180559945326786870974425873986894274700919374605593673636620279660269393102485807676278657875062914284858935724438e-301", false, false);
yield return new("Exp2M1", 1e-300, 0.0, "6.93147180559945326786870974425873986894274700919374605593673636620279660269393102485807676278657875062914284858935724438e-301", false, false);
yield return new("Exp2M1", -2.2250738585072014e-308, 0.0, "-1.54230367156190531855910393712847096456890294742459890370396553388262870291588706079233655504875900365429627823154008399e-308", false, false);
yield return new("Exp2M1", 2.2250738585072014e-308, 0.0, "1.54230367156190531855910393712847096456890294742459890370396553388262870291588706079233655504875900365429627823154008399e-308", false, false);
yield return new("Exp2M1", -1e-323, 0.0, "-6.84920418852777021427470999061380292158364356780264127745978767817293643334458446499873465218026337654986814054831649295e-324", false, false);
yield return new("Exp2M1", 1e-323, 0.0, "6.84920418852777021427470999061380292158364356780264127745978767817293643334458446499873465218026337654986814054831649295e-324", false, false);
yield return new("Exp2M1", -5e-324, 0.0, "-3.42460209426388510713735499530690146079182178390132063872989383908646821667229223249936732609013168827493407027415824647e-324", false, false);
yield return new("Exp2M1", 5e-324, 0.0, "3.42460209426388510713735499530690146079182178390132063872989383908646821667229223249936732609013168827493407027415824647e-324", false, false);
// Exp2M1: absolute-log-half-switch
yield return new("Exp2M1", -0.7213475204444817, -1.0177636870465518e-17, "-3.93469340287366576396200465008820417438099792541202652192428640510984866996467126609246899789618731498509990689191240306e-1", false, false);
yield return new("Exp2M1", -0.7213475204444817, -1.0177636870465517e-17, "-3.93469340287366576396200465008819769686204001884905656907964381721986981887448485558283931634670370961885669141173839528e-1", false, false);
yield return new("Exp2M1", -0.7213475204444817, -1.0177636870465515e-17, "-3.93469340287366576396200465008819121934308211228608661623500122932297322155364322708244878742394153333622619986830147957e-1", false, false);
yield return new("Exp2M1", 0.7213475204444817, 1.0177636870465515e-17, "6.48721270700128146848650787814162417406688247281002602415805950728600029067384069417210842542030614979026168642397668251e-1", false, false);
yield return new("Exp2M1", 0.7213475204444817, 1.0177636870465517e-17, "6.48721270700128146848650787814164178178895924919128376762790092850572717154172663805292837189744464569525528256254871274e-1", false, false);
yield return new("Exp2M1", 0.7213475204444817, 1.0177636870465518e-17, "6.48721270700128146848650787814165938951103602557254151109774234974425843628229371597802606772669504937810418198966839579e-1", false, false);
// Exp2M1: input-high-series-switch
yield return new("Exp2M1", -0.5000000000000001, 0.0, "-2.92893218813452530014413685649703955167858165405676925733374390585360532699651656717067373024344502855553936702073773310e-1", false, false);
yield return new("Exp2M1", -0.5, 0.0, "-2.92893218813452475599155637895150960715164062311525963411660131004633760768946480574806232836179213632493076884543851488e-1", false, false);
yield return new("Exp2M1", -0.49999999999999994, 0.0, "-2.92893218813452448391526614017872893170662533251599103883499092807605318109978129696795891336476533197262084559728907784e-1", false, false);
yield return new("Exp2M1", -0.5, -2.7755575615628914e-17, "-2.92893218813452489202970149833789601907876207463268995864344371593967907826653024658969061732863275170576563234829479896e-1", false, false);
yield return new("Exp2M1", -0.5, 2.7755575615628914e-17, "-2.92893218813452461995341125956512057802759504240971011133505105055334106579225243377019120609626314024272048921794865070e-1", false, false);
// Exp2M1: quadratic-series-switch
yield return new("Exp2M1", -8.008566259537294e-17, -1.129944678986474e-33, "-5.55111512312578254804376278947525210997941054353275279722773648933255116925354923236018969777309126680622446520049198092e-17", false, false);
yield return new("Exp2M1", -8.008566259537294e-17, -1.1299446789864738e-33, "-5.55111512312578254804376278947524025321574270017331675503562752039604580100758672073788499063969008912857194783169179131e-17", false, false);
yield return new("Exp2M1", -8.008566259537294e-17, -1.1299446789864737e-33, "-5.55111512312578254804376278947522839645207485681388071284351855145954043276162420770975183675538569412491789275246025718e-17", false, false);
// Exp2M1: input-high-series-switch
yield return new("Exp2M1", 0.49999999999999994, 0.0, "4.14213562373094994386430676455144037238208120607589712724123944476593389531373838698287076714161408538277123570716809775e-1", false, false);
yield return new("Exp2M1", 0.5, 0.0, "4.14213562373095048801688724209698078569671875376948073176679737990732478462107038850387534327641572735013846230912297025e-1", false, false);
yield return new("Exp2M1", 0.5000000000000001, 0.0, "4.14213562373095157632204819718812442505217294967231432235173388674957037169706704669406056199264812251701573141419622976e-1", false, false);
yield return new("Exp2M1", 0.5, -2.7755575615628914e-17, "4.14213562373095021594059700332420796184247585073462008271311256812064184346693950682061876534273449658846873530341040209e-1", false, false);
yield return new("Exp2M1", 0.5, 2.7755575615628914e-17, "4.14213562373095076009317748086975884394480991518057977732989789889331786841549513245961758780747371951455902156410269860e-1", false, false);
// Exp2M1: quadratic-series-switch
yield return new("Exp2M1", 8.008566259537294e-17, 1.1299446789864737e-33, "5.55111512312578285619255389143296488540854567040146788574745400787982691625510656198196538826488518799624108409547153570e-17", false, false);
yield return new("Exp2M1", 8.008566259537294e-17, 1.1299446789864738e-33, "5.55111512312578285619255389143297674217221351376222029314172084883206635846687879210449178738034357857131792648832026482e-17", false, false);
yield return new("Exp2M1", 8.008566259537294e-17, 1.129944678986474e-33, "5.55111512312578285619255389143298859893588135712297270053598768978430580067865102363284663324670534255070733217896828964e-17", false, false);
// Exp2M1: random-moderate
yield return new("Exp2M1", 8.78452800194328, -4.440892098500626e-16, "4.39967341476995196084861795619177036986833446346865068997727335172090686266840323292806805468322290875886544488139632805e+2", false, false);
yield return new("Exp2M1", 2.7202982684631807, 1.1102230246251565e-16, "5.59009045860470968459866968889737028605350979742874890837108301124392819835145507472654892784605100090392179416858372658e+0", false, false);
yield return new("Exp2M1", -3.4144589655515833, 1.1102230246251565e-16, "-9.06212397865302249501452386547149636442561439125894793225413740615446561503933959009917468149743846696349004220925890981e-1", false, false);
yield return new("Exp2M1", 3.67643978981615, 1.1102230246251565e-16, "1.17855275975436677510222614327660438420381423323350572823327975657260641462810932407195792018571153800381071193261125222e+1", false, false);
yield return new("Exp2M1", 7.559099749783131, -2.220446049250313e-16, "1.87588742600978100098560391503204728439903232936850014376858946537478147694222110588671754338725081209043377353414161445e+2", false, false);
yield return new("Exp2M1", 1.1272538511782404, 5.551115123125783e-17, "1.18442542093619461179505632322739881400577647657120007022725830276626252983657181904591357904539459020575218758691298325e+0", false, false);
// Exp2M1: random-range
yield return new("Exp2M1", -816.2214079897565, -2.842170943040401e-14, "-1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp2M1", -678.2241907878085, -2.842170943040401e-14, "-1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp2M1", -370.71699147959754, -1.4210854715202004e-14, "-9.99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999747031956e-1", false, false);
yield return new("Exp2M1", 325.3049427158436, 1.4210854715202004e-14, "8.44394693210093550736509533332028359812000552799434020131118049620518568591929165240041457560841455860244163260586337592e+97", false, false);
yield return new("Exp2M1", 956.9782395104039, 2.842170943040401e-14, "1.19992828387600858106093631065960712137870691174075367927710136390487281510229839467771780838917585057717098231260902141e+288", false, false);
yield return new("Exp2M1", 364.3581585496251, -1.4210854715202004e-14, "4.81653686139180239709139190167158242071136853423964187582462482485565537885773294376979018541476826443148951887942014775e+109", false, false);
// Exp2M1: integer-power
yield return new("Exp2M1", -1076.0, 0.0, "-1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp2M1", -1075.0, 0.0, "-1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp2M1", -1074.0, 0.0, "-1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp2M1", -1022.0, 0.0, "-1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp2M1", -100.0, 0.0, "-9.999999999999999999999999999992111390947789881945882714347172137703267935648909769952297210693359375e-1", false, false);
yield return new("Exp2M1", 10.0, 0.0, "1.023e+3", false, false);
yield return new("Exp2M1", 53.0, 0.0, "9.007199254740991e+15", false, false);
yield return new("Exp2M1", 100.0, 0.0, "1.267650600228229401496703205375e+30", false, false);
yield return new("Exp2M1", 1023.0, 0.0, "8.98846567431157953864652595394512366808988489471153286367150405788663379027504815663542386612037680105600569399356966788e+307", false, false);
yield return new("Exp2M1", 1024.0, 0.0, "1.79769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393358e+308", true, false);
// Exp2M1: negative-saturation
yield return new("Exp2M1", -54.0, 0.0, "-9.99999999999999944488848768742172978818416595458984375e-1", false, false);
// Exp2M1: negative-saturation-sparse-low
yield return new("Exp2M1", -1075.0, -5e-324, "-1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp2M1", -1075.0, 5e-324, "-1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
// Exp2M1: overflow-adjacent-low
yield return new("Exp2M1", 1024.0, -8.008566259537295e-17, "1.79769313486231580793728971405302296962136452782486920563609826833849467898852832283391964137375521264887063753644165960e+308", false, false);
yield return new("Exp2M1", 1024.0, -8.008566259537294e-17, "1.79769313486231580793728971405303832856973767181831156142887561931500193969224300858151352073314340505642260157691456128e+308", true, false);
yield return new("Exp2M1", 1024.0, -8.008566259537293e-17, "1.79769313486231580793728971405305368751811081581175391722165297042273142656692749614371314702169582876170167447259768366e+308", true, false);
// Exp10M1: ordinary
yield return new("Exp10M1", -10.25, 0.0, "-9.99999999943765867480965091960504896022351876853174895690130833591831057626411643135693715109414201547377969407132389268e-1", false, false);
yield return new("Exp10M1", -2.0, 0.0, "-9.9e-1", false, false);
yield return new("Exp10M1", -1.0, 0.0, "-9e-1", false, false);
yield return new("Exp10M1", -0.125, 0.0, "-2.50105790667544172697815724384863561558132081835028985379580994570172474832839372019326304016855443753407915992275941455e-1", false, false);
yield return new("Exp10M1", 0.125, 0.0, "3.33521432163324025675931715295331092415667964764370993329549987162758943180195818649013498004732558877445661357678378086e-1", false, false);
yield return new("Exp10M1", 0.75, 0.0, "4.62341325190349080394951039776481231468251043098691664081689423735883568643062848905857984526220305928676107320100325218e+0", false, false);
yield return new("Exp10M1", 1.0, 0.0, "9e+0", false, false);
yield return new("Exp10M1", 3.25, 0.0, "1.77727941003892280122542119519268484473579052640225535801183072277630188153949380490030039927870215508827904815953580779e+3", false, false);
// Exp10M1: dense-and-sparse-low
yield return new("Exp10M1", -1.0, 5.551115123125783e-17, "-8.99999999999999987218085067996765462754319617590916607329559738303097631479573890845779454801469327857031874845490804547e-1", false, false);
yield return new("Exp10M1", -1.0, -5.551115123125783e-17, "-9.00000000000000012781914932003232903472187092736774381834984894590193923691417843226363938571113470345052880638874691579e-1", false, false);
yield return new("Exp10M1", -1.0, 5e-324, "-9.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e-1", false, false);
yield return new("Exp10M1", -1.0, -5e-324, "-9.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e-1", false, false);
yield return new("Exp10M1", 1.0, 5.551115123125783e-17, "9.00000000000000127819149320032345372456803824090833926704402616969023685204261091542205451985306721429681251545091954530e+0", false, false);
yield return new("Exp10M1", 1.0, -5.551115123125783e-17, "8.99999999999999872180850679967670965278129072632256181650151054098060763085821567736360614288865296549471193611253084211e+0", false, false);
yield return new("Exp10M1", 1.0, 5e-324, "9.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp10M1", 1.0, -5e-324, "9.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
// Exp10M1: tiny
yield return new("Exp10M1", -1e-20, 0.0, "-2.30258509299404555770222241510393875598524143224381069825640182231193232027551245827252136404378259029970201038081656945e-20", false, false);
yield return new("Exp10M1", 1e-20, 0.0, "2.30258509299404555775524139620872273027502276727279330790399614334475670162688907681242268603731733932824173589898447578e-20", false, false);
yield return new("Exp10M1", -1e-100, 0.0, "-2.30258509299404573005104192078836854051056555890772672956315725895733581426274915955308976822915813062782353584671664263e-100", false, false);
yield return new("Exp10M1", 1e-100, 0.0, "2.30258509299404573005104192078836854051056555890772672956315725895733581426274915955308976822915813115801334689455646489e-100", false, false);
yield return new("Exp10M1", -1e-300, 0.0, "-2.30258509299404574171868275840485705189541108963464621458993298511568529915114775677970158135301609867111333610627501526e-300", false, false);
yield return new("Exp10M1", 1e-300, 0.0, "2.30258509299404574171868275840485705189541108963464621458993298511568529915114775677970158135301609867111333610627501526e-300", false, false);
yield return new("Exp10M1", -2.2250738585072014e-308, 0.0, "-5.12342189740942434501569327901461575239505617669057681130375993183183004105261803650543347093083043244937774062452497400e-308", false, false);
yield return new("Exp10M1", 2.2250738585072014e-308, 0.0, "5.12342189740942434501569327901461575239505617669057681130375993183183004105261803650543347093083043244937774062452497400e-308", false, false);
yield return new("Exp10M1", -1e-323, 0.0, "-2.27525638214905982837507870853537983552919151750274587233835884873293322219317237853594514215541907751178107438528862527e-323", false, false);
yield return new("Exp10M1", 1e-323, 0.0, "2.27525638214905982837507870853537983552919151750274587233835884873293322219317237853594514215541907751178107438528862527e-323", false, false);
yield return new("Exp10M1", -5e-324, 0.0, "-1.13762819107452991418753935426768991776459575875137293616917942436646661109658618926797257107770953875589053719264431264e-323", false, false);
yield return new("Exp10M1", 5e-324, 0.0, "1.13762819107452991418753935426768991776459575875137293616917942436646661109658618926797257107770953875589053719264431264e-323", false, false);
// Exp10M1: absolute-log-half-switch
yield return new("Exp10M1", -0.2171472409516259, -5.491598251083826e-18, "-3.93469340287366576396200465008820362882072578421007457645958090117166064388468963572181401431536248330076974888414600998e-1", false, false);
yield return new("Exp10M1", -0.2171472409516259, -5.491598251083825e-18, "-3.93469340287366576396200465008819286989462006654938071200708034981872180311176975058969796694858078679858192326373506407e-1", false, false);
yield return new("Exp10M1", -0.2171472409516259, -5.4915982510838246e-18, "-3.93469340287366576396200465008818211096851434888868684755457979844669827319829825582055598674633324019908386971626983556e-1", false, false);
yield return new("Exp10M1", 0.2171472409516259, 5.4915982510838246e-18, "6.48721270700128146848650787814159941493780812305632348189724146484756143871422484272097497594536517379427525706613712148e-1", false, false);
yield return new("Exp10M1", 0.2171472409516259, 5.491598251083825e-18, "6.48721270700128146848650787814162866073113502901405270661424684716022537920234692762763311713810662847016175115314168844e-1", false, false);
yield return new("Exp10M1", 0.2171472409516259, 5.491598251083826e-18, "6.48721270700128146848650787814165790652446193497178193133125222952476688338302012652507096358970116413935923203943027614e-1", false, false);
// Exp10M1: input-high-series-switch
yield return new("Exp10M1", -0.12500000000000003, 0.0, "-2.50105790667544220623235682831241227091366353848874384997791525723347737745979450739687740450604812196950274788445358612e-1", false, false);
yield return new("Exp10M1", -0.12499999999999999, 0.0, "-2.50105790667544148735105745161673580206563090718922547131556925027730093155363334658244471038575458141695010455686722771e-1", false, false);
yield return new("Exp10M1", -0.125, -6.938893903907228e-18, "-2.50105790667544184679170713996458265087678613615781682045379956151380255522669580317417278415513574852965788877157596059e-1", false, false);
yield return new("Exp10M1", -0.125, 6.938893903907228e-18, "-2.50105790667544160716460734773268666597760240869410293263525178017542720692692645153465620057550733979128107066362824524e-1", false, false);
// Exp10M1: quadratic-series-switch
yield return new("Exp10M1", -2.4108186663832177e-17, -6.096898820344505e-34, "-5.55111512312578254804376278947525111135952916952488118057090928416625710539857409417218238146094275651639899718827039890e-17", false, false);
yield return new("Exp10M1", -2.4108186663832177e-17, -6.0968988203445044e-34, "-5.55111512312578254804376278947523141770135784523200699580201696017754023733808975824261769382695015710450861125941702211e-17", false, false);
yield return new("Exp10M1", -2.4108186663832177e-17, -6.096898820344504e-34, "-5.55111512312578254804376278947521172404318652093913281103312463618882336927760541843465128450327642975730308116022193819e-17", false, false);
// Exp10M1: input-high-series-switch
yield return new("Exp10M1", 0.12499999999999999, 0.0, "3.33521432163323983063537950508510118386148151192037635826279917554245850263297432726335583118584038111090819342091499132e-1", false, false);
yield return new("Exp10M1", 0.12500000000000003, 0.0, "3.33521432163324110900719244868977125484648720885341763163649432278606530942581178394286995865756427602775067340009630110e-1", false, false);
yield return new("Exp10M1", 0.125, -6.938893903907228e-18, "3.33521432163324004369734832901920435192160510937529511447041291879436130189385376993868097672364644153343456779246847864e-1", false, false);
yield return new("Exp10M1", 0.125, 6.938893903907228e-18, "3.33521432163324046982128597688742090056670512672567520458135576124210663897996160077897850952474308729078332610355280926e-1", false, false);
// Exp10M1: quadratic-series-switch
yield return new("Exp10M1", 2.4108186663832177e-17, 6.096898820344504e-34, "5.55111512312578285619255389143294821299965733452486897471468053264121209511768939103939182765135013753615105042745685340e-17", false, false);
yield return new("Exp10M1", 2.4108186663832177e-17, 6.0968988203445044e-34, "5.55111512312578285619255389143296790665782865881992959475766301532431147684229694003121733250823665487940329649836608834e-17", false, false);
yield return new("Exp10M1", 2.4108186663832177e-17, 6.096898820344505e-34, "5.55111512312578285619255389143298760031599998311499021480064549800741085856690449290144455905480473074705970331287834858e-17", false, false);
// Exp10M1: random-moderate
yield return new("Exp10M1", -2.9677357165710085, -1.1102230246251565e-16, "-9.98922879520165034714208159852150757557324583422419498005448293567172081929540244921649768419178178105615911287043460489e-1", false, false);
yield return new("Exp10M1", 0.5995899657426765, -2.7755575615628914e-17, "2.97731479519890352879887600772233924298609686175885392875139750363632580171965156242522363738652933349600506618119299140e+0", false, false);
yield return new("Exp10M1", 0.8836851964549527, -2.7755575615628914e-17, "6.65041856144971858829962647061748349456294701097773926744045197071139270643977619590118903694461196808465346045233138263e+0", false, false);
yield return new("Exp10M1", -0.89395009436352, -2.7755575615628914e-17, "-8.72341450465680217809052941648826847479618841723966114669093459382270793548409479815284018448824046387929644197921673353e-1", false, false);
yield return new("Exp10M1", 2.4580147142578235, -1.1102230246251565e-16, "2.86087784810333896489677828661765156448804286447564220961070487382944745109858177125943722517534602403829765495652338124e+2", false, false);
yield return new("Exp10M1", 1.3118516264245281, 5.551115123125783e-17, "1.95046153376223160193906750616367118569485139159211538092010333804564633619337531743249262303443651659625057726925490890e+1", false, false);
// Exp10M1: random-range
yield return new("Exp10M1", 120.58969150830279, 3.552713678800501e-15, "3.88768893311729066468034240903516540867755441511370722931132770507875572390376800025883066808459259868657675470129694955e+120", false, false);
yield return new("Exp10M1", 128.81118142762747, -7.105427357601002e-15, "6.47413017703446171983916778641885806651744797264667468232608054450205762476305583291383873034783101822647556402661401770e+128", false, false);
yield return new("Exp10M1", 42.94992135127048, 1.7763568394002505e-15, "8.91089551309232685378712887186962248614334667700386757268709525041370952477415725129562497023716824045224543686275322633e+42", false, false);
yield return new("Exp10M1", 116.81408221381518, -3.552713678800501e-15, "6.51751761797928744146122574992738336073782813915351110967844224366907044327161884077729178445278842875234452038465776427e+116", false, false);
yield return new("Exp10M1", 228.96449953361966, -7.105427357601002e-15, "9.21508899144045996730372464171725339874292277767159122312745668742022577561608598769932754185213295353703683516203829072e+228", false, false);
yield return new("Exp10M1", 7.524609271856807, 2.220446049250313e-16, "3.34664201369735369097716632117090216287459757705675042241703792408934408612576386525573391905086740939515500212837287795e+7", false, false);
// Exp10M1: integer-power
yield return new("Exp10M1", -324.0, 0.0, "-1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp10M1", -323.0, 0.0, "-1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp10M1", -308.0, 0.0, "-1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0", false, false);
yield return new("Exp10M1", 22.0, 0.0, "9.999999999999999999999e+21", false, false);
yield return new("Exp10M1", 308.0, 0.0, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+308", false, false);
yield return new("Exp10M1", 309.0, 0.0, "1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+309", true, false);
// Exp10M1: negative-saturation
yield return new("Exp10M1", -20.0, 0.0, "-9.9999999999999999999e-1", false, false);
yield return new("Exp10M1", -100.0, 0.0, "-9.999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999e-1", false, false);
// Exp10M1: overflow-adjacent-low
yield return new("Exp10M1", 308.25471555991675, -2.895125789515847e-15, "1.79769313486231580793728971405135339792924520420229515147549215774814264965213204794170642362509327672779802464860968196e+308", false, false);
yield return new("Exp10M1", 308.25471555991675, -2.8951257895158466e-15, "1.79769313486231580793728971405298608023672269474350236922097481966929437654805569985823689453925527448616186188699371312e+308", false, false);
yield return new("Exp10M1", 308.25471555991675, -2.8951257895158462e-15, "1.79769313486231580793728971405461876254420018528470958696645896440832277858828718822634729443205898882595653371847623943e+308", true, false);
}
}
@@ -0,0 +1,270 @@
"""Independent stdlib references for the five new exponential-family functions.
Run: python3 path/to/generate_exponential_functions.py [--check]
Exact binary64 high+low sums are formed at 2200 digits and checked with Fraction.
Decimal ln/exp (or a cancellation-safe expm1 series) runs at 450 and 650 digits;
all inputs, flags, and 120-significant-digit references must agree. Integer base-2
and base-10 powers use exact rational arithmetic, also checked against ln/exp.
No production arithmetic, binary64 transcendental functions, or rounded DD
constants are used. Zeros, nonfinite inputs, and enormous inputs belong in the
main suite. These rows specify an error bound, not correctly rounded DD results.
"""
from collections import Counter
from decimal import Decimal, localcontext
from fractions import Fraction
from pathlib import Path
import argparse
import math
import random
OUTPUT = Path(__file__).with_name('ExponentialFunctionsReferenceData.cs')
OPERATIONS = {'Exp2': 2, 'Exp10': 10, 'ExpM1': None, 'Exp2M1': 2, 'Exp10M1': 10}
EPSILON = math.ulp(0.0)
def exact_sum(high, low):
with localcontext() as context:
context.prec = 2200
value = Decimal.from_float(high) + Decimal.from_float(low)
assert Fraction(value) == Fraction(high) + Fraction(low), (high, low)
return value
def exact_decimal(value):
with localcontext() as context:
context.prec = 2200
result = Decimal(value.numerator) / Decimal(value.denominator)
assert Fraction(result) == value
return result
OVERFLOW = exact_decimal(Fraction(2) ** 1024 - Fraction(2) ** 970)
HALF_EPSILON = exact_decimal(Fraction(2) ** -1075)
def expm1(value):
# exp(value)-1 would erase tiny inputs even with hundreds of digits. For
# |value| <= 0.5, sum x + x^2/2! + ... until the context stops changing.
if abs(value) > Decimal('0.5'):
return value.exp() - 1
total = term = value
for denominator in range(2, 10000):
term = term * value / denominator
updated = total + term
if updated == total:
return updated
total = updated
raise AssertionError('expm1 series did not converge')
def neighbors(boundary):
high = float(boundary)
low = float(boundary - Decimal.from_float(high))
adjacent = [math.nextafter(low, -math.inf), low, math.nextafter(low, math.inf)]
assert exact_sum(high, adjacent[0]) < boundary < exact_sum(high, adjacent[-1])
assert math.nextafter(adjacent[0], math.inf) == adjacent[1]
assert math.nextafter(adjacent[1], math.inf) == adjacent[2]
return [(high, residual) for residual in adjacent]
def inputs(operation, log_base):
base = OPERATIONS[operation]
minus_one = operation.endswith('M1')
for high in [-10.25, -2.0, -1.0, -0.125, 0.125, 0.75, 1.0, 3.25]:
yield 'ordinary', high, 0.0
for high in [-1.0, 1.0]:
for low in [math.ulp(high) / 4, -math.ulp(high) / 4, EPSILON, -EPSILON]:
yield 'dense-and-sparse-low', high, low
for magnitude in [1e-20, 1e-100, 1e-300, math.ldexp(1.0, -1022), 2 * EPSILON, EPSILON]:
for sign in [-1, 1]:
yield 'tiny', sign * magnitude, 0.0
for sign in [-1, 1]:
for high, low in neighbors(Decimal(sign) / (2 * log_base)):
yield 'absolute-log-half-switch', high, low
if minus_one:
# Concrete dispatch uses the input high (0.5 for e/2, 0.125 for 10).
# Include both neighboring highs and lows on either side of the anchor.
switch = 0.125 if base == 10 else 0.5
for sign in [-1, 1]:
high = sign * switch
for adjacent in [math.nextafter(high, -math.inf), high, math.nextafter(high, math.inf)]:
yield 'input-high-series-switch', adjacent, 0.0
for low in [-math.ulp(high) / 4, math.ulp(high) / 4]:
yield 'input-high-series-switch', high, low
for high, low in neighbors(Decimal(sign) * Decimal(2) ** -54 / log_base):
yield 'quadratic-series-switch', high, low
if operation == 'Exp2':
for sign in [-1, 1]:
low = sign * math.ldexp(1.0, -500)
for adjacent in [math.nextafter(low, -math.inf), low, math.nextafter(low, math.inf)]:
yield 'sparse-correction-switch', 1000.0, adjacent
rng = random.Random(20260916 + (base or 1))
# Both local/moderate arguments and the complete useful exponential output
# range, expressed in natural-log coordinates, with reproducible dense lows.
for label, lower, upper in [('random-moderate', -8, 8), ('random-range', -748, 712)]:
for _ in range(6):
high = float(Decimal.from_float(rng.uniform(lower, upper)) / log_base)
yield label, high, rng.choice([-1, 1]) * math.ulp(high) / 4
if base == 2:
for exponent in [-1076, -1075, -1074, -1022, -100, -1, 1, 10, 53, 100, 1023, 1024]:
yield 'integer-power', float(exponent), 0.0
elif base == 10:
for exponent in [-324, -323, -308, -1, 1, 22, 308, 309]:
yield 'integer-power', float(exponent), 0.0
if minus_one:
for high in ([-40.0, -100.0, -745.0, -746.0] if base is None else
[-54.0, -100.0, -1075.0] if base == 2 else [-20.0, -100.0, -324.0]):
yield 'negative-saturation', high, 0.0
if base == 2:
# The exponential correction is exactly half epsilon at -1075,
# but exp2m1 is near -1, NOT an underflowing result.
for low in [-EPSILON, EPSILON]:
yield 'negative-saturation-sparse-low', -1075.0, low
# For M1, exp(x ln b) must reach OVERFLOW + 1, not OVERFLOW.
overflow_log = (OVERFLOW + int(minus_one)).ln() / log_base
for high, low in neighbors(overflow_log):
yield 'overflow-adjacent-low', high, low
if not minus_one:
# Base 2 has the exactly attainable threshold x=-1075; do not obtain
# it by an inexact quotient of two rounded logarithms.
underflow_log = Decimal(-1075) if base == 2 else HALF_EPSILON.ln() / log_base
for high, low in neighbors(underflow_log):
yield 'underflow-adjacent-low', high, low
normal_log = Decimal(-1022) if base == 2 else Decimal(2).ln() * -1022 / log_base
for high, low in neighbors(normal_log):
yield 'min-normal-adjacent-low', high, low
def evaluate(operation, high, low, log_base):
value = exact_sum(high, low)
base = OPERATIONS[operation]
minus_one = operation.endswith('M1')
argument = value * log_base
result = expm1(argument) if minus_one else argument.exp()
if base is not None and value == value.to_integral_value():
rational = Fraction(base) ** int(value) - int(minus_one)
exact = exact_decimal(rational)
# Cross-check integer fixtures by an independent mathematical identity.
assert abs(result - exact) <= abs(exact) * Decimal('1e-400')
result = exact
# Use exact input versus the independently computed thresholds: comparing a
# rounded exponential with half epsilon can misclassify an exact midpoint.
overflow = value >= (OVERFLOW + int(minus_one)).ln() / log_base
if minus_one:
underflow = abs(result) <= HALF_EPSILON
assert (result < 0) == (value < 0)
else:
threshold = Decimal(-1075) if base == 2 else HALF_EPSILON.ln() / log_base
underflow = value <= threshold
with localcontext() as output_context:
output_context.prec = 120
rounded = +result
assert abs(result - rounded) <= abs(result) * Decimal(2) ** -350
return format(rounded, 'e'), overflow, underflow
def generate_at_precision(precision):
rows = []
counts = Counter()
with localcontext() as context:
context.prec = precision
for operation, base in OPERATIONS.items():
log_base = Decimal(base).ln() if base else Decimal(1)
seen = set()
boundary_flags = {}
for label, high, low in inputs(operation, log_base):
assert math.isfinite(high) and math.isfinite(low)
assert high != 0 and abs(low) <= math.ulp(high) / 2
assert float(Fraction(high) + Fraction(low)) == high, (high, low)
result = evaluate(operation, high, low, log_base)
if label in ['overflow-adjacent-low', 'underflow-adjacent-low']:
flag_index = 1 if label.startswith('overflow') else 2
boundary_flags.setdefault(label, []).append(result[flag_index])
key = (high, low)
if key in seen:
continue
seen.add(key)
rows.append((operation, label, high, low, *result))
counts[operation] += 1
assert boundary_flags['overflow-adjacent-low'][0] is False
assert boundary_flags['overflow-adjacent-low'][-1] is True
if not operation.endswith('M1'):
assert boundary_flags['underflow-adjacent-low'][0] is True
assert boundary_flags['underflow-adjacent-low'][-1] is False
return rows, counts
def verify_sparse_splits(precision):
# Component-retention sentinels in DoubleDoubleExponentialFunctionsTests:
# unlike the 120-digit rows, these retain the tiny correction beside 2^n.
with localcontext() as context:
context.prec = precision
ln2 = Decimal(2).ln()
for exponent, expected_low in [(1, EPSILON), (500, 1.1210060331144859e-173),
(1000, 3.6694906201918696e-23)]:
for sign in [-1, 1]:
value = exact_sum(float(exponent), sign * EPSILON)
result = (value * ln2).exp()
high = math.ldexp(1.0, exponent)
assert float(result) == high
assert float(result - Decimal.from_float(high)) == sign * expected_low
def generate():
first, counts = generate_at_precision(450)
second, second_counts = generate_at_precision(650)
assert first == second and counts == second_counts, 'Precision stability check failed'
verify_sparse_splits(450)
verify_sparse_splits(650)
assert 200 <= len(first) <= 400
lines = []
previous_group = None
for operation, label, high, low, reference, overflow, underflow in first:
group = operation, label
if group != previous_group:
lines.append(f' // {operation}: {label}')
previous_group = group
lines.append(f' yield return new("{operation}", {high!r}, {low!r}, '
f'"{reference}", {str(overflow).lower()}, {str(underflow).lower()});')
content = '''// Generated by generate_exponential_functions.py; do not hand-edit.
// Exact binary64 sums verified with Fraction at 2200 digits; Decimal ln/exp and
// cancellation-safe expm1 at 450/650 digits; 120-digit references agree.
// Integer base-2/base-10 cases additionally use exact rational powers.
// Row: operation, high, low, reference, overflow, underflow.
// Overflow: exact result >= 2^1024 - 2^970 (binary64 overflow midpoint).
// Underflow: |exact result| <= 2^-1075, NOT exp(x) <= 2^-1075 for M1.
// Compare exact component sums with relative tolerance 2^-100 + double.Epsilon
// absolute; reference rounding uncertainty is bounded by 2^-350 relative.
// Tiny corrections to 1/-1 below 120 digits are not component-retention oracles.
// Signed zeros, infinities, NaNs, and enormous arguments are tested separately.
using Xunit;
namespace Just.PreciseMath.Tests.ReferenceData;
internal static class ExponentialFunctionsReferenceData
{
internal static IEnumerable<TheoryDataRow<string, double, double, string, bool, bool>> Cases()
{
''' + '\n'.join(lines) + '\n }\n}\n'
return content, counts
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--check', action='store_true', help='Verify fixtures without writing')
args = parser.parse_args()
content, counts = generate()
if args.check:
assert OUTPUT.read_text() == content, 'Fixture is stale; regenerate it'
action = 'Verified'
else:
OUTPUT.write_text(content)
action = 'Generated'
print(f'{action} {sum(counts.values())} references at 450/650 digits: {dict(counts)}')
print('Exact 2200-digit sums, rational integer powers, adjacent-low boundary flags, '
'normalized pairs, and reference rounding bounds verified.')
if __name__ == '__main__':
main()
+33 -7
View File
@@ -93,7 +93,11 @@ constants. The natural logarithm function is provided separately by `DDMath.Log`
Root functions are implemented on `DoubleDouble` in `DoubleDouble.RootFunctions.cs`: Root functions are implemented on `DoubleDouble` in `DoubleDouble.RootFunctions.cs`:
`Sqrt`, `Cbrt`, `Hypot`, and `RootN` complete `IRootFunctions<DoubleDouble>`. `Sqrt`, `Cbrt`, `Hypot`, and `RootN` complete `IRootFunctions<DoubleDouble>`.
The additional `InvSqrt` helper and three-argument `Hypot` overload live alongside The additional `InvSqrt` helper and three-argument `Hypot` overload live alongside
them. `DDMath` exposes thin forwarding wrappers; the other mathematical kernels them. Exponential functions live in `DoubleDouble.ExponentialFunctions.cs`:
`Exp`, `Exp2`, `Exp10`, `ExpM1`, `Exp2M1`, and `Exp10M1` complete
`IExponentialFunctions<DoubleDouble>`, including cancellation-safe overrides of
the interface's default minus-one methods. `DDMath` exposes thin forwarding
wrappers for roots and exponentials; logarithm, power, and reciprocal kernels
remain in `DDMath`. remain in `DDMath`.
The `DDMath` static class provides: The `DDMath` static class provides:
@@ -163,6 +167,20 @@ The `DDMath` static class provides:
components affect range boundaries; representable subnormals are retained. components affect range boundaries; representable subnormals are retained.
Either zero maps to one, negative infinity to positive zero, positive infinity Either zero maps to one, negative infinity to positive zero, positive infinity
to positive infinity, and NaN to canonical NaN. to positive infinity, and NaN to canonical NaN.
- `Exp2(DoubleDouble)`: reduces the argument in base two before evaluating a
bounded natural exponential. Integer powers from -1074 through 1023 are exact.
An exponent-tracked correction preserves sparse lows that would otherwise round
prematurely before final scaling. The exact half-minimum-subnormal threshold
uses both input components. Special-value behavior is the same as `Exp`.
- `Exp10(DoubleDouble)`: subtracts three split `log10(2)` products before converting
the bounded remainder to a natural exponent, avoiding large-argument amplification
of a rounded `ln(10)` product. Special-value behavior is the same as `Exp`.
- `ExpM1(DoubleDouble)`, `Exp2M1(DoubleDouble)`, and `Exp10M1(DoubleDouble)`:
compute the corresponding exponential minus one, using a direct series near
zero to avoid cancellation. Signed zeros are preserved, negative infinity maps
to negative one, positive infinity to positive infinity, and NaN to canonical NaN.
Their tested relative error is measured against the minus-one result itself,
not against the exponential before subtraction.
- `Log(DoubleDouble)`: natural logarithm using binary range reduction and a - `Log(DoubleDouble)`: natural logarithm using binary range reduction and a
centered atanh series. The bounded mantissa avoids denominator overflow for centered atanh series. The bounded mantissa avoids denominator overflow for
large inputs; a separate near-one path retains even minimum-subnormal low large inputs; a separate near-one path retains even minimum-subnormal low
@@ -181,7 +199,10 @@ DoubleDouble fifthRoot = DoubleDouble.RootN(new DoubleDouble(2.0), 5);
DoubleDouble reciprocal = DDMath.Reciprocal(new DoubleDouble(3.0)); DoubleDouble reciprocal = DDMath.Reciprocal(new DoubleDouble(3.0));
DoubleDouble magnitude = DDMath.Abs(-root); DoubleDouble magnitude = DDMath.Abs(-root);
DoubleDouble smallPower = DDMath.Pow(new DoubleDouble(2.0), -1024); DoubleDouble smallPower = DDMath.Pow(new DoubleDouble(2.0), -1024);
DoubleDouble exponential = DDMath.Exp(new DoubleDouble(1.0)); DoubleDouble exponential = DoubleDouble.Exp(new DoubleDouble(1.0));
DoubleDouble binaryPower = DoubleDouble.Exp2(new DoubleDouble(-1000.0));
DoubleDouble decimalPower = DoubleDouble.Exp10(new DoubleDouble(0.5));
DoubleDouble tinyChange = DoubleDouble.ExpM1(new DoubleDouble(1e-30));
DoubleDouble logarithm = DDMath.Log(new DoubleDouble(10.0)); DoubleDouble logarithm = DDMath.Log(new DoubleDouble(10.0));
DoubleDouble fractionalPower = DDMath.Pow(new DoubleDouble(2.0), 0.5); DoubleDouble fractionalPower = DDMath.Pow(new DoubleDouble(2.0), 0.5);
DoubleDouble preciseExponent = DoubleDouble.FromComponents(0.5, 1e-30); DoubleDouble preciseExponent = DoubleDouble.FromComponents(0.5, 1e-30);
@@ -225,13 +246,17 @@ additional tests cover extreme magnitudes, near-one cancellation, sparse lows of
either sign, and range-reduction transitions. This is sampled approximate accuracy, either sign, and range-reduction transitions. This is sampled approximate accuracy,
not a universal error proof or a correct-rounding guarantee. not a universal error proof or a correct-rounding guarantee.
Exponential tests use independent high-precision decimal references evaluated at Exponential-family tests use independent high-precision decimal references evaluated at
two precisions, with exact integer comparisons of the stored component sum. They two precisions, with exact integer comparisons of the stored component sum. They
check `2^-100` relative error plus one minimum binary64 subnormal, with a separately check `2^-100` relative error plus one minimum binary64 subnormal, with a separately
bounded reference-rounding allowance. Integer-power tests use exact rational bounded reference-rounding allowance. The base-two/base-ten and minus-one fixtures
are checked at 450/650 digits, including adjacent lows at true range boundaries,
series transitions, and tiny inputs. Additional component checks pin sparse
base-two corrections; constrained generic calls and facade calls match direct
calls bit-for-bit. Integer-power tests use exact rational
references and high-precision fixtures for large exponents; their tested absolute references and high-precision fixtures for large exponents; their tested absolute
error bound is `|exact result| * (|exponent| + 1) * 2^-100 + 2^-1074`, not uniform error bound is `|exact result| * (|exponent| + 1) * 2^-100 + 2^-1074`, not uniform
relative accuracy independent of the exponent. Both functions are approximate; relative accuracy independent of the exponent. These functions are approximate;
precision decreases near underflow and approximation can affect results extremely precision decreases near underflow and approximation can affect results extremely
close to a rounding boundary. Final scaling uses the existing allocating exact close to a rounding boundary. Final scaling uses the existing allocating exact
boundary machinery where necessary. No performance measurements are claimed. boundary machinery where necessary. No performance measurements are claimed.
@@ -342,9 +367,10 @@ bool success = DoubleDouble.TryParse("1.25e-2".AsSpan(), CultureInfo.InvariantCu
## Deferred scope ## Deferred scope
Natural `DDMath.Log`, `Exp`, and all three `Pow` overloads are implemented. Natural `DDMath.Log`, the complete exponential family on `DoubleDouble`, and all
three `DDMath.Pow` overloads are implemented.
Logarithms in other bases, generic-math interfaces beyond `ISignedNumber`, Logarithms in other bases, generic-math interfaces beyond `ISignedNumber`,
`IFloatingPointConstants`, and `IRootFunctions`, additional text formats/general `IFloatingPointConstants`, `IRootFunctions`, and `IExponentialFunctions`, additional text formats/general
round-trip formatting, and non-arithmetic performance benchmarks remain deferred. round-trip formatting, and non-arithmetic performance benchmarks remain deferred.
Replacing allocating arithmetic boundary fallbacks is also deferred; the current Replacing allocating arithmetic boundary fallbacks is also deferred; the current
`BigInteger` paths remain in place. That optimization does not require removing `BigInteger` paths remain in place. That optimization does not require removing