This commit is contained in:
@@ -16,7 +16,7 @@ public readonly partial struct DoubleDouble : IExponentialFunctions<DoubleDouble
|
||||
{
|
||||
if (double.IsNaN(value.High))
|
||||
{
|
||||
return DoubleDouble.NaN;
|
||||
return NaN;
|
||||
}
|
||||
// These deliberately loose bounds only reject inputs safely outside the
|
||||
// result range, even with a normalized low of either sign. They also keep
|
||||
@@ -27,11 +27,11 @@ public readonly partial struct DoubleDouble : IExponentialFunctions<DoubleDouble
|
||||
}
|
||||
if (value.High < -746.0)
|
||||
{
|
||||
return DoubleDouble.Zero;
|
||||
return Zero;
|
||||
}
|
||||
if (value.High == 0.0)
|
||||
{
|
||||
return DoubleDouble.One;
|
||||
return One;
|
||||
}
|
||||
|
||||
// The binary64 estimate need not choose the nearest k on a tie: either
|
||||
@@ -40,9 +40,9 @@ public readonly partial struct DoubleDouble : IExponentialFunctions<DoubleDouble
|
||||
// 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;
|
||||
int exponent = (int)Math.Round(value.High / Ln2.High);
|
||||
DoubleDouble reduced = value - (new DoubleDouble(Ln2.High) * exponent);
|
||||
reduced -= new DoubleDouble(Ln2.Low) * exponent;
|
||||
reduced -= new DoubleDouble(ln2Tail) * exponent;
|
||||
|
||||
return PreciseMathHelper.ScalePowerOfTwo(ExpReduced(reduced), exponent);
|
||||
@@ -225,8 +225,8 @@ public readonly partial struct DoubleDouble : IExponentialFunctions<DoubleDouble
|
||||
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;
|
||||
DoubleDouble numerator = One;
|
||||
DoubleDouble denominator = One;
|
||||
foreach (double coefficient in coefficients)
|
||||
{
|
||||
numerator = (numerator * reduced) + coefficient;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Just.PreciseMath;
|
||||
|
||||
public readonly partial struct DoubleDouble
|
||||
public readonly partial struct DoubleDouble : ILogarithmicFunctions<DoubleDouble>
|
||||
{
|
||||
/// <summary>Returns the natural logarithm of a double-double value.</summary>
|
||||
/// <remarks>
|
||||
@@ -34,6 +34,110 @@ public readonly partial struct DoubleDouble
|
||||
return LogPositiveFinite(value);
|
||||
}
|
||||
|
||||
/// <summary>Returns the logarithm of a value in the specified base.</summary>
|
||||
/// <remarks>
|
||||
/// Uses the quotient of natural logarithms, retaining both components of each
|
||||
/// operand. Negative inputs or bases, NaN, and base one return canonical NaN.
|
||||
/// A zero or positive-infinite base is allowed only for value one, returning
|
||||
/// negative or positive zero respectively. For other positive bases, zero and
|
||||
/// infinity follow the signs of log(value)/log(newBase), including signed zero
|
||||
/// for value one. Finite results are approximate, not guaranteed correctly rounded;
|
||||
/// tests check 2^-100 relative error plus one minimum binary64 subnormal.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
public static DoubleDouble Log(DoubleDouble value, DoubleDouble newBase)
|
||||
{
|
||||
if (IsNaN(value) || IsNaN(newBase) || newBase == One
|
||||
|| (value != One && (IsZero(newBase) || IsPositiveInfinity(newBase))))
|
||||
{
|
||||
return NaN;
|
||||
}
|
||||
return Log(value) / Log(newBase);
|
||||
}
|
||||
|
||||
/// <summary>Returns the base-two logarithm of a double-double value.</summary>
|
||||
/// <remarks>
|
||||
/// Special values and approximate accuracy follow <see cref="Log(DoubleDouble)"/>.
|
||||
/// Representable powers of two return their exact integer exponent.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
public static DoubleDouble Log2(DoubleDouble value)
|
||||
{
|
||||
if (value.High > 0.0 && double.IsFinite(value.High) && value.Low == 0.0)
|
||||
{
|
||||
int exponent = Math.ILogB(value.High);
|
||||
if (value.High == Math.ScaleB(1.0, exponent))
|
||||
{
|
||||
return new DoubleDouble(exponent);
|
||||
}
|
||||
}
|
||||
return Log(value) / Ln2;
|
||||
}
|
||||
|
||||
/// <summary>Returns the base-ten logarithm of a double-double value.</summary>
|
||||
/// <remarks>
|
||||
/// Special values and approximate accuracy follow <see cref="Log(DoubleDouble)"/>.
|
||||
/// Uses the natural logarithm divided by ln(10), without collapsing components.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
public static DoubleDouble Log10(DoubleDouble value) => Log(value) / Ln10;
|
||||
|
||||
/// <summary>Returns the natural logarithm of one plus a double-double value.</summary>
|
||||
/// <remarks>
|
||||
/// Evaluates a direct atanh series near zero, without rounding away either
|
||||
/// input component by adding one first. Signed zeros are preserved; minus one
|
||||
/// returns negative infinity; values below minus one and NaN return canonical
|
||||
/// NaN; positive infinity is preserved. Approximate accuracy follows
|
||||
/// <see cref="Log(DoubleDouble)"/>, relative to log(1+value) itself.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
public static DoubleDouble LogP1(DoubleDouble value)
|
||||
{
|
||||
if (IsZero(value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
if (Math.Abs(value.High) <= Math.ScaleB(1.0, -54))
|
||||
{
|
||||
// log(1+x) = x-x²/2+O(x³), with omitted relative error < 2^-109.
|
||||
// Keep x unscaled so even minimum-subnormal inputs survive.
|
||||
return value - ((value * value) * 0.5);
|
||||
}
|
||||
if (Math.Abs(value.High) <= 0.5)
|
||||
{
|
||||
// |t| <= 1/3 plus endpoint rounding. Unlike Log(1+x), the numerator
|
||||
// is the original expansion, retaining both components near zero.
|
||||
DoubleDouble t = value / (value + 2.0);
|
||||
DoubleDouble squared = t * t;
|
||||
DoubleDouble term = t;
|
||||
DoubleDouble sum = t;
|
||||
for (int denominator = 3; denominator <= 69; denominator += 2)
|
||||
{
|
||||
term *= squared;
|
||||
sum += term / denominator;
|
||||
}
|
||||
// The omitted tail is <= 2*|t|^71/(71*(1-t²)); rounding dominates.
|
||||
return sum * 2.0;
|
||||
}
|
||||
return Log(value + 1.0);
|
||||
}
|
||||
|
||||
/// <summary>Returns the base-two logarithm of one plus a double-double value.</summary>
|
||||
/// <remarks>
|
||||
/// Uses the cancellation-safe natural logarithm divided by ln(2). Domain,
|
||||
/// signed-zero, and approximate accuracy rules follow <see cref="LogP1"/>.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
public static DoubleDouble Log2P1(DoubleDouble value) => LogP1(value) / Ln2;
|
||||
|
||||
/// <summary>Returns the base-ten logarithm of one plus a double-double value.</summary>
|
||||
/// <remarks>
|
||||
/// Uses the cancellation-safe natural logarithm divided by ln(10). Domain,
|
||||
/// signed-zero, and approximate accuracy rules follow <see cref="LogP1"/>.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
public static DoubleDouble Log10P1(DoubleDouble value) => LogP1(value) / Ln10;
|
||||
|
||||
// Shared with Pow. Requires a positive finite normalized value other than one;
|
||||
// callers handle domain errors and special values before any range reduction.
|
||||
private static DoubleDouble LogPositiveFinite(DoubleDouble value)
|
||||
|
||||
@@ -20,7 +20,7 @@ public readonly partial struct DoubleDouble
|
||||
public static DoubleDouble Reciprocal(DoubleDouble value)
|
||||
{
|
||||
const double one = 1.0;
|
||||
if (!DoubleDouble.IsFinite(value) || value._high == 0.0)
|
||||
if (!IsFinite(value) || value._high == 0.0)
|
||||
{
|
||||
return new DoubleDouble(one / value._high);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Just.PreciseMath;
|
||||
|
||||
public readonly partial struct DoubleDouble
|
||||
public readonly partial struct DoubleDouble : IPowerFunctions<DoubleDouble>
|
||||
{
|
||||
/// <summary>Raises a double-double value to an integer power.</summary>
|
||||
/// <remarks>
|
||||
|
||||
@@ -256,7 +256,7 @@ public readonly partial struct DoubleDouble : IRootFunctions<DoubleDouble>
|
||||
{
|
||||
if (double.IsNaN(value.High) || value.High < 0.0)
|
||||
{
|
||||
return DoubleDouble.NaN;
|
||||
return NaN;
|
||||
}
|
||||
if (value.High == 0.0 || double.IsPositiveInfinity(value.High))
|
||||
{
|
||||
@@ -316,7 +316,7 @@ public readonly partial struct DoubleDouble : IRootFunctions<DoubleDouble>
|
||||
{
|
||||
if (double.IsNaN(value.High) || value.High < 0.0)
|
||||
{
|
||||
return DoubleDouble.NaN;
|
||||
return NaN;
|
||||
}
|
||||
if (value.High == 0.0)
|
||||
{
|
||||
@@ -324,7 +324,7 @@ public readonly partial struct DoubleDouble : IRootFunctions<DoubleDouble>
|
||||
}
|
||||
if (double.IsPositiveInfinity(value.High))
|
||||
{
|
||||
return DoubleDouble.Zero;
|
||||
return Zero;
|
||||
}
|
||||
|
||||
// As in Sqrt, the even exponent is in [-1074, 1022], high is in [1, 4),
|
||||
@@ -345,13 +345,13 @@ public readonly partial struct DoubleDouble : IRootFunctions<DoubleDouble>
|
||||
residual = Math.FusedMultiplyAdd(-high, squareError, residual);
|
||||
residual = Math.FusedMultiplyAdd(-low, square, residual);
|
||||
double correction = (0.5 * estimate) * residual;
|
||||
DoubleDouble inverseRoot = DoubleDouble.FromComponents(estimate, correction);
|
||||
DoubleDouble inverseRoot = FromComponents(estimate, correction);
|
||||
|
||||
// The result high is normal and finite over the entire positive input
|
||||
// range. Rescale in the opposite direction to Sqrt and canonicalize any
|
||||
// zero low, including underflow of an exceptionally sparse correction.
|
||||
int inverseExponent = -(exponent / 2);
|
||||
return DoubleDouble.FromComponents(Math.ScaleB(inverseRoot.High, inverseExponent),
|
||||
return FromComponents(Math.ScaleB(inverseRoot.High, inverseExponent),
|
||||
Math.ScaleB(inverseRoot.Low, inverseExponent));
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,30 @@ public static class DoubleDoubleExtensions
|
||||
[Pure]
|
||||
public static DoubleDouble Log(this DoubleDouble value) => DoubleDouble.Log(value);
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Log(DoubleDouble, DoubleDouble)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Log(this DoubleDouble value, DoubleDouble newBase) => DoubleDouble.Log(value, newBase);
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Log2(DoubleDouble)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Log2(this DoubleDouble value) => DoubleDouble.Log2(value);
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Log10(DoubleDouble)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Log10(this DoubleDouble value) => DoubleDouble.Log10(value);
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.LogP1(DoubleDouble)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble LogP1(this DoubleDouble value) => DoubleDouble.LogP1(value);
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Log2P1(DoubleDouble)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Log2P1(this DoubleDouble value) => DoubleDouble.Log2P1(value);
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Log10P1(DoubleDouble)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Log10P1(this DoubleDouble value) => DoubleDouble.Log10P1(value);
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.RootN(DoubleDouble, int)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble RootN(this DoubleDouble value, int n) => DoubleDouble.RootN(value, n);
|
||||
|
||||
Reference in New Issue
Block a user