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
31 changes: 31 additions & 0 deletions PreciseNumber.Test/PreciseNumberTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,37 @@ public void TestRoundRoundsHalfAwayFromZeroOnTheDroppedDigits()
}
}

[TestMethod]
public void TestRoundToNegativeDecimalsDoesNotDependOnHowTheValueIsStored()
{
static PreciseNumber P(string text) => PreciseNumber.Parse(text, CultureInfo.InvariantCulture);

// Integers are stored with trailing zeros moved into a positive exponent, so "1230" is 123 at
// exponent 1. Rounding used to derive the dropped digits from the decimal places alone, which
// only worked when the value had a fractional part.
(string Input, int Decimals, string Expected)[] cases =
[
("1234.5", -2, "1200"),
("1234", -2, "1200"),
("1230", -2, "1200"),
("1250", -2, "1300"),
("1500", -3, "2000"),
("-1500", -3, "-2000"),
("1499", -3, "1000"),
("400", -3, "0"),
("1200", -2, "1200"),
("1234", 0, "1234"),
("1230", 2, "1230"),
("7", -1, "10"),
];

foreach ((string input, int decimals, string expected) in cases)
{
Assert.AreEqual(P(expected), P(input).Round(decimals), $"{input} to {decimals} decimal places");
Assert.AreEqual(P(expected), PreciseNumber.Round(P(input), decimals), $"static Round of {input} to {decimals} decimal places");
}
}

[TestMethod]
public void TestMakeCommonizedAndGetExponent()
{
Expand Down
19 changes: 13 additions & 6 deletions PreciseNumber/PreciseNumber.cs
Original file line number Diff line number Diff line change
Expand Up @@ -544,21 +544,28 @@
/// </summary>
/// <param name="decimalDigits">The number of decimal digits to round to.</param>
/// <returns>A new instance of <see cref="PreciseNumber"/> rounded to the specified number of decimal digits.</returns>
/// <remarks>Rounds half away from zero, so 1.235 becomes 1.24 and 1.2349 becomes 1.23.</remarks>
/// <remarks>
/// Rounds half away from zero, so 1.235 becomes 1.24 and 1.2349 becomes 1.23. A negative
/// <paramref name="decimalDigits"/> rounds to a place left of the units, so 1234 rounded to -2
/// becomes 1200 and 1500 rounded to -3 becomes 2000.
/// </remarks>
public PreciseNumber Round(int decimalDigits)
{
long currentDecimalDigits = CountDecimalDigits();
long decimalDifference = long.Abs(decimalDigits - currentDecimalDigits);
if (currentDecimalDigits > decimalDigits && decimalDifference > 0)
// The requested place, as the exponent of its digit. Anything stored below it is dropped.
// Working from this rather than from CountDecimalDigits keeps the arithmetic right whatever
// the sign of the stored exponent: a value with trailing zeros, stored with a positive
// exponent, has its last digit that many places left of the units.
long targetExponent = -(long)decimalDigits;
if (Exponent < targetExponent)
{
// Dropping one digit more than the significand holds always leaves zero, so there is
// never a reason to raise ten to a wider power than that, however far below the
// requested place the value sits.
int droppedDigits = (int)long.Min(decimalDifference, SignificantDigits + 1);
int droppedDigits = (int)long.Min(targetExponent - Exponent, SignificantDigits + 1);
BigInteger newSignificand = DropDigitsRoundingHalfAwayFromZero(Significand, droppedDigits);
int newExponent = newSignificand.IsZero
? 0
: Exponent - int.CopySign(droppedDigits, Exponent);
: checked(Exponent + droppedDigits);
return new PreciseNumber(newExponent, newSignificand);
}

Expand Down Expand Up @@ -697,7 +704,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 707 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 707 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 707 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 707 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 707 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 707 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 +1118,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 1121 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 1121 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 1121 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 1121 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
Loading