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
20 changes: 20 additions & 0 deletions PreciseNumber.Test/PreciseNumberTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2674,6 +2674,26 @@ public void TestMultiplyThrowsOverflowWhenTheExponentLeavesTheIntRange()
Assert.AreEqual(PreciseNumber.One, big * small);
}

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

// The exponents are 4e9 apart; the difference wrapped to -294967296 and reached Pow10 as an
// ArgumentOutOfRangeException, unlike the OverflowException Multiply and Divide throw.
Assert.ThrowsExactly<OverflowException>(() => big + small);
Assert.ThrowsExactly<OverflowException>(() => small + big);
Assert.ThrowsExactly<OverflowException>(() => big - small);
Assert.ThrowsExactly<OverflowException>(() => small - big);
Assert.ThrowsExactly<OverflowException>(() => big % small);
Assert.ThrowsExactly<OverflowException>(() => small % big);

// Exponents that are close enough to align are unaffected.
Assert.AreEqual(PreciseNumber.Parse("2e2000000000", NumberStyles.Float, CultureInfo.InvariantCulture), big + big);
Assert.AreEqual(PreciseNumber.Zero, small - small);
}

[TestMethod]
public void TestDivideThrowsOverflowWhenTheExponentLeavesTheIntRange()
{
Expand Down
10 changes: 8 additions & 2 deletions PreciseNumber/PreciseNumber.cs
Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,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 734 in PreciseNumber/PreciseNumber.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Remove this method and declare a constant for this value.

Check warning on line 734 in PreciseNumber/PreciseNumber.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

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

Expand Down Expand Up @@ -943,6 +943,7 @@
/// <param name="left">The left number.</param>
/// <param name="right">The right number.</param>
/// <returns>The scaled significands and the exponent they share.</returns>
/// <exception cref="OverflowException">Thrown when the exponents are further apart than the range of an <see cref="int"/>.</exception>
private static (BigInteger Left, BigInteger Right, int Exponent) CommonizeSignificands(PreciseNumber left, PreciseNumber right)
{
int leftExponent = left.Exponent;
Expand All @@ -953,9 +954,11 @@
return (left.Significand, right.Significand, leftExponent);
}

// Exponents at opposite extremes are more than int.MaxValue apart, and an unchecked difference
// would wrap negative and reach Pow10 as an unrelated ArgumentOutOfRangeException.
return leftExponent > rightExponent
? (left.Significand * Pow10(leftExponent - rightExponent), right.Significand, rightExponent)
: (left.Significand, right.Significand * Pow10(rightExponent - leftExponent), leftExponent);
? (left.Significand * Pow10(checked(leftExponent - rightExponent)), right.Significand, rightExponent)
: (left.Significand, right.Significand * Pow10(checked(rightExponent - leftExponent)), leftExponent);
}

/// <summary>
Expand Down Expand Up @@ -1189,7 +1192,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 1195 in PreciseNumber/PreciseNumber.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

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

Check warning on line 1195 in PreciseNumber/PreciseNumber.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

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

Check warning on line 1195 in PreciseNumber/PreciseNumber.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

All 'Parse' method overloads should be adjacent.

Check warning on line 1195 in PreciseNumber/PreciseNumber.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

All 'Parse' method overloads should be adjacent.
{
// Read the separator and signs from the same culture TryFormat writes them with, so that
// Parse(x.ToString(p), p) round-trips for every provider.
Expand Down Expand Up @@ -1239,14 +1242,14 @@
}

hasDecimal = true;
i += decimalSeparator.Length - 1;

Check warning on line 1245 in PreciseNumber/PreciseNumber.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Do not update the stop condition variable 'i' in the body of the for loop.

Check warning on line 1245 in PreciseNumber/PreciseNumber.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Do not update the stop condition variable 'i' in the body of the for loop.
continue;
}

// Group separators may only sit between integral digits.
if (allowThousands && !hasDecimal && digitCount > 0 && s[i..].StartsWith(groupSeparator, StringComparison.Ordinal))
{
i += groupSeparator.Length - 1;

Check warning on line 1252 in PreciseNumber/PreciseNumber.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Do not update the stop condition variable 'i' in the body of the for loop.

Check warning on line 1252 in PreciseNumber/PreciseNumber.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Do not update the stop condition variable 'i' in the body of the for loop.
continue;
}

Expand Down Expand Up @@ -1536,6 +1539,7 @@
/// <param name="left">The number to subtract from.</param>
/// <param name="right">The number to subtract.</param>
/// <returns>The result of the subtraction.</returns>
/// <exception cref="OverflowException">Thrown when the exponents of the operands are further apart than the range of an <see cref="int"/>.</exception>
public static PreciseNumber Subtract(PreciseNumber left, PreciseNumber right)
{
(BigInteger commonLeft, BigInteger commonRight, int commonExponent) = CommonizeSignificands(left, right);
Expand All @@ -1548,6 +1552,7 @@
/// <param name="left">The first number to add.</param>
/// <param name="right">The second number to add.</param>
/// <returns>The result of the addition.</returns>
/// <exception cref="OverflowException">Thrown when the exponents of the operands are further apart than the range of an <see cref="int"/>.</exception>
public static PreciseNumber Add(PreciseNumber left, PreciseNumber right)
{
(BigInteger commonLeft, BigInteger commonRight, int commonExponent) = CommonizeSignificands(left, right);
Expand Down Expand Up @@ -1809,6 +1814,7 @@
/// <param name="left">The number to divide.</param>
/// <param name="right">The number to divide by.</param>
/// <returns>The modulus of the two numbers.</returns>
/// <exception cref="OverflowException">Thrown when the exponents of the operands are further apart than the range of an <see cref="int"/>.</exception>
public static PreciseNumber Mod(PreciseNumber left, PreciseNumber right)
{
if (right.Significand.IsZero)
Expand Down
Loading