logarithmic functions implementation
.NET Test / .NET tests (push) Successful in 2m14s

This commit is contained in:
2026-09-24 01:23:09 +04:00
parent 720fd1e21f
commit cb06df8865
11 changed files with 1944 additions and 22 deletions
@@ -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;
+105 -1
View File
@@ -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);