Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions PreciseNumber.Test/PreciseNumberTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2430,6 +2430,47 @@ public void TestParseThenFormatAtExtremeExponentsRoundTrips()
Assert.ThrowsExactly<OverflowException>(() => huge.ToString(CultureInfo.InvariantCulture));
}

[TestMethod]
public void TestMultiplyThrowsOverflowWhenTheExponentLeavesTheIntRange()
{
PreciseNumber big = PreciseNumber.Parse("1e2000000000", NumberStyles.Float, CultureInfo.InvariantCulture);
PreciseNumber small = PreciseNumber.Parse("1e-2000000000", NumberStyles.Float, CultureInfo.InvariantCulture);

// Wrapping would give 1e-294967296 and 1e+294967296, off by billions of orders of magnitude.
Assert.ThrowsExactly<OverflowException>(() => big * big);
Assert.ThrowsExactly<OverflowException>(() => small * small);
Assert.ThrowsExactly<OverflowException>(() => PreciseNumber.Pow(big, 2.ToPreciseNumber()));

// Exponents that still fit are unaffected.
Assert.AreEqual(PreciseNumber.One, big * small);
}

[TestMethod]
public void TestDivideThrowsOverflowWhenTheExponentLeavesTheIntRange()
{
PreciseNumber big = PreciseNumber.Parse("1e2000000000", NumberStyles.Float, CultureInfo.InvariantCulture);
PreciseNumber small = PreciseNumber.Parse("1e-2000000000", NumberStyles.Float, CultureInfo.InvariantCulture);

Assert.ThrowsExactly<OverflowException>(() => big / small);
Assert.ThrowsExactly<OverflowException>(() => small / big);

// Exponents that still fit are unaffected.
Assert.AreEqual(PreciseNumber.One, big / big);
}

[TestMethod]
public void TestDivideThrowsOverflowWhenScalingTheQuotientLeavesTheIntRange()
{
PreciseNumber tiny = PreciseNumber.Parse("1E-2147483648", NumberStyles.Float, CultureInfo.InvariantCulture);

// An exact quotient: 1e-2147483648 / 2 is 5e-2147483649, one place past int.MinValue.
Assert.ThrowsExactly<OverflowException>(() => tiny / PreciseNumber.CreateFromComponents(0, 2));

// A rounded quotient: a third needs its digits below int.MinValue too.
Assert.ThrowsExactly<OverflowException>(() => tiny / PreciseNumber.CreateFromComponents(0, 3));
Assert.ThrowsExactly<OverflowException>(() => PreciseNumber.Divide(tiny, PreciseNumber.CreateFromComponents(0, 3), 5));
}

[TestMethod]
public void TestRoundAtExtremeNegativeExponentGivesZero()
{
Expand Down
13 changes: 8 additions & 5 deletions PreciseNumber/PreciseNumber.cs
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,7 @@
/// A fixed precision such as <c>E15</c> rounds values that need 17 digits, which turns
/// <see cref="double.MaxValue"/> into a number that converts back to infinity.
/// </remarks>
internal static string GetStringFormatForFloatType<TFloat>()

Check warning on line 700 in PreciseNumber/PreciseNumber.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Remove this method and declare a constant for this value.

Check warning on line 700 in PreciseNumber/PreciseNumber.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Remove this method and declare a constant for this value.

Check warning on line 700 in PreciseNumber/PreciseNumber.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Remove this method and declare a constant for this value.

Check warning on line 700 in PreciseNumber/PreciseNumber.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Remove this method and declare a constant for this value.

Check warning on line 700 in PreciseNumber/PreciseNumber.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Remove this method and declare a constant for this value.

Check warning on line 700 in PreciseNumber/PreciseNumber.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Remove this method and declare a constant for this value.
where TFloat : INumber<TFloat>
=> "R";

Expand Down Expand Up @@ -1111,7 +1111,7 @@
public static PreciseNumber MinMagnitudeNumber(PreciseNumber x, PreciseNumber y) => MinMagnitude(x, y);

/// <inheritdoc/>
public static PreciseNumber Parse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider)

Check warning on line 1114 in PreciseNumber/PreciseNumber.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Refactor this method to reduce its Cognitive Complexity from 23 to the 15 allowed.

Check warning on line 1114 in PreciseNumber/PreciseNumber.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Refactor this method to reduce its Cognitive Complexity from 23 to the 15 allowed.

Check warning on line 1114 in PreciseNumber/PreciseNumber.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Refactor this method to reduce its Cognitive Complexity from 23 to the 15 allowed.

Check warning on line 1114 in PreciseNumber/PreciseNumber.cs

View workflow job for this annotation

GitHub Actions / Analyze & Release

Refactor this method to reduce its Cognitive Complexity from 23 to the 15 allowed.
{
if (s.IsEmpty)
{
Expand Down Expand Up @@ -1409,6 +1409,7 @@
/// <param name="left">The first number to multiply.</param>
/// <param name="right">The second number to multiply.</param>
/// <returns>The result of the multiplication.</returns>
/// <exception cref="OverflowException">Thrown when the product needs an exponent outside the range of an <see cref="int"/>.</exception>
public static PreciseNumber Multiply(PreciseNumber left, PreciseNumber right)
{
if (left.Significand.IsZero || right.Significand.IsZero)
Expand All @@ -1426,7 +1427,7 @@

// (l * 10^el) * (r * 10^er) == (l * r) * 10^(el + er), so there is no need to scale the
// operands to a common exponent first; doing so only inflates both significands.
return new PreciseNumber(left.Exponent + right.Exponent, left.Significand * right.Significand);
return new PreciseNumber(checked(left.Exponent + right.Exponent), left.Significand * right.Significand);
}

/// <summary>
Expand All @@ -1436,6 +1437,7 @@
/// <param name="right">The number to divide by.</param>
/// <returns>The result of the division.</returns>
/// <exception cref="DivideByZeroException">Thrown when <paramref name="right"/> is zero.</exception>
/// <exception cref="OverflowException">Thrown when the quotient needs an exponent outside the range of an <see cref="int"/>.</exception>
/// <remarks>
/// A quotient whose decimal expansion terminates is produced exactly, however many digits that
/// takes. One that repeats is produced to the precision of the wider operand, and never fewer
Expand Down Expand Up @@ -1465,6 +1467,7 @@
/// <returns>The result of the division.</returns>
/// <exception cref="DivideByZeroException">Thrown when <paramref name="right"/> is zero.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="significantDigits"/> is less than one.</exception>
/// <exception cref="OverflowException">Thrown when the quotient needs an exponent outside the range of an <see cref="int"/>.</exception>
public static PreciseNumber Divide(PreciseNumber left, PreciseNumber right, int significantDigits)
{
if (significantDigits < 1)
Expand All @@ -1484,7 +1487,7 @@

BigInteger numerator = left.Significand;
BigInteger denominator = right.Significand;
int exponent = left.Exponent - right.Exponent;
int exponent = checked(left.Exponent - right.Exponent);

// Carry the sign on the numerator so the denominator can be factorized as a positive value.
if (denominator.Sign < 0)
Expand Down Expand Up @@ -1543,7 +1546,7 @@
int scale = Math.Max(twos, fives);
BigInteger significand = numerator * BigInteger.Pow(2, scale - twos) * BigInteger.Pow(5, scale - fives);

result = new PreciseNumber(exponent - scale, significand);
result = new PreciseNumber(checked(exponent - scale), significand);
return true;
}

Expand All @@ -1562,7 +1565,7 @@
// rounding decision is made on.
int scale = significantDigits + 1 - CountDigits(numerator) + CountDigits(denominator);
BigInteger scaled = scale > 0 ? numerator * Pow10(scale) : numerator;
int scaledExponent = exponent - Math.Max(scale, 0);
int scaledExponent = checked(exponent - Math.Max(scale, 0));

BigInteger quotient = scaled / denominator;
int excess = CountDigits(quotient) - significantDigits;
Expand All @@ -1583,7 +1586,7 @@
kept += quotient.Sign;
}

return new PreciseNumber(scaledExponent + excess, kept);
return new PreciseNumber(checked(scaledExponent + excess), kept);
}

/// <summary>
Expand Down
Loading