@@ -0,0 +1,120 @@
|
||||
using System.Numerics;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace Just.PreciseMath.Tests;
|
||||
|
||||
public class DoubleDoubleCbrtTests
|
||||
{
|
||||
[Fact]
|
||||
public void ExactCubesAndSpecialValues()
|
||||
{
|
||||
foreach (double value in new[] { 0.0, -0.0, double.NaN, double.PositiveInfinity, double.NegativeInfinity })
|
||||
{
|
||||
DoubleDouble result = DoubleDouble.Cbrt(new DoubleDouble(value));
|
||||
AssertBits(new DoubleDouble(Math.Cbrt(value)), result);
|
||||
}
|
||||
foreach (double root in new[] { -3.0, -2.0, -1.0, 0.5, 1.0, 2.0, 3.0 })
|
||||
{
|
||||
AssertBits(new DoubleDouble(root), DoubleDouble.Cbrt(new DoubleDouble(root * root * root)));
|
||||
}
|
||||
for (int exponent = -1074; exponent <= 1023; exponent += 3)
|
||||
{
|
||||
DoubleDouble input = new(Math.ScaleB(1.0, exponent));
|
||||
AssertBits(new DoubleDouble(Math.ScaleB(1.0, exponent / 3)), DoubleDouble.Cbrt(input));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CubesMeetExactBoundAcrossEveryInputExponent()
|
||||
{
|
||||
Random random = new(271828);
|
||||
for (int exponent = -1074; exponent <= 1023; ++exponent)
|
||||
{
|
||||
double high = Math.ScaleB(1.0 + random.NextDouble(), exponent);
|
||||
double dense = Math.ScaleB(random.NextDouble(), exponent - 53);
|
||||
foreach (double low in new[] { 0.0, dense, -dense, double.Epsilon, -double.Epsilon })
|
||||
{
|
||||
DoubleDouble input = DoubleDouble.FromComponents(high, low);
|
||||
if (input.High > 0.0 && DoubleDouble.IsFinite(input))
|
||||
{
|
||||
AssertBound(input, DoubleDouble.Cbrt(input));
|
||||
AssertBound(-input, DoubleDouble.Cbrt(-input));
|
||||
AssertBits(DoubleDouble.Cbrt(input), DDMath.Cbrt(input));
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (double low in new[] { 0.0, Math.BitDecrement(Math.ScaleB(1.0, 970)), -Math.ScaleB(1.0, 970) })
|
||||
{
|
||||
DoubleDouble input = DoubleDouble.FromComponents(double.MaxValue, low);
|
||||
AssertBound(input, DoubleDouble.Cbrt(input));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SparseNearOneCorrectionsSurvive()
|
||||
{
|
||||
// cbrt(1+d) = 1+d/3+O(d^2); for these d, the quadratic is
|
||||
// below half an ulp of the rounded binary64 correction d/3.
|
||||
foreach (int exponent in new[] { -100, -500, -1000, -1072 })
|
||||
{
|
||||
foreach (double sign in new[] { -1.0, 1.0 })
|
||||
{
|
||||
double low = Math.ScaleB(sign, exponent);
|
||||
DoubleDouble result = DoubleDouble.Cbrt(DoubleDouble.FromComponents(1.0, low));
|
||||
result.High.ShouldBe(1.0);
|
||||
result.Low.ShouldBe(low / 3.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScalingDoesNotEraseARepresentableSparseCorrection()
|
||||
{
|
||||
// cbrt(2^900 + d) = 2^300 + d/(3*2^600) + O(d²/2^1500).
|
||||
// Scaling d=2^-174 by 2^-900 leaves epsilon, whose division by 3
|
||||
// underflows, although the final correction 2^-774/3 is representable.
|
||||
foreach (double sign in new[] { -1.0, 1.0 })
|
||||
{
|
||||
DoubleDouble input = DoubleDouble.FromComponents(Math.ScaleB(1.0, 900), Math.ScaleB(sign, -174));
|
||||
DoubleDouble result = DoubleDouble.Cbrt(input);
|
||||
AssertBits(DoubleDouble.FromComponents(Math.ScaleB(1.0, 300), Math.ScaleB(sign / 3.0, -774)), result);
|
||||
AssertBits(-result, DoubleDouble.Cbrt(-input));
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertBound(DoubleDouble input, DoubleDouble actual)
|
||||
{
|
||||
DoubleDouble.IsFinite(actual).ShouldBeTrue();
|
||||
DoubleDouble.IsCanonical(actual).ShouldBeTrue();
|
||||
Math.Sign(actual.High).ShouldBe(Math.Sign(input.High));
|
||||
// Exact dyadic oracle, independent of the implementation's arithmetic:
|
||||
// x*(1-t)^3 <= |y|^3 <= x*(1+t)^3, t=2^-100, x=|input|.
|
||||
BigInteger x = BigInteger.Abs(Units(input.High) + Units(input.Low)) << 2148;
|
||||
BigInteger y = BigInteger.Abs(Units(actual.High) + Units(actual.Low));
|
||||
BigInteger scale = BigInteger.One << 100;
|
||||
BigInteger cube = BigInteger.Pow(y, 3) << 300;
|
||||
(cube >= x * BigInteger.Pow(scale - 1, 3)
|
||||
&& cube <= x * BigInteger.Pow(scale + 1, 3)).ShouldBeTrue(
|
||||
$"Cbrt bound failed for ({input.High:R}, {input.Low:R}): ({actual.High:R}, {actual.Low:R})");
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user