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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

## Features

- Addition and subtraction round to the fewest decimal places among the operands, and multiplication, division, and modulus round to the fewest significant digits
- Addition, subtraction, and modulus round to the fewest decimal places among the operands, and multiplication and division round to the fewest significant digits
- Operands of exactly -1, 0, or 1 have unlimited precision, so they never limit a result
- A `readonly record struct` whose `default` is zero, holding a `PreciseNumber` that it converts to implicitly
- Implements `INumber<SignificantNumber>`, including `CreateChecked`, `CreateSaturating`, and `CreateTruncating` for every built-in numeric type, `BigInteger`, and `PreciseNumber`
Expand Down
49 changes: 49 additions & 0 deletions SignificantNumber.Test/SignificantNumberModTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) 2023-2026 ktsu-dev contributors

namespace SignificantNumber.Test;

using System.Globalization;
using ktsu.SignificantNumber;

/// <summary>
/// Covers the rounding of <see cref="SignificantNumber.Mod"/>, whose result must stay smaller than the divisor.
/// </summary>
[TestClass]
public class SignificantNumberModTests
{
private static SignificantNumber Parse(string text) =>
SignificantNumber.Parse(text, NumberStyles.Float, CultureInfo.InvariantCulture);

[TestMethod]
[DataRow("19.9", "2", "0")]
[DataRow("9.96", "5", "0")]
[DataRow("-19.9", "2", "0")]
[DataRow("6.8", "2.27", "0")]
public void Mod_RemainderRoundingUpToDivisor_ReturnsZero(string left, string right, string expected) =>
Assert.AreEqual(Parse(expected), Parse(left) % Parse(right));

[TestMethod]
[DataRow("19.9", "1.5", "0.4")]
[DataRow("-19.9", "1.5", "-0.4")]
[DataRow("7.25", "2", "1")]
[DataRow("250", "70", "40")]
[DataRow("10.123", "0.5", "0.1")]
public void Mod_RoundsToFewestDecimalPlaces(string left, string right, string expected) =>
Assert.AreEqual(Parse(expected), Parse(left) % Parse(right));

[TestMethod]
[DataRow("19.9", "2")]
[DataRow("9.96", "5")]
[DataRow("-19.9", "2")]
[DataRow("19.9", "-2")]
[DataRow("6.8", "2.27")]
[DataRow("123.456", "0.7")]
[DataRow("1.99", "0.2")]
public void Mod_ResultMagnitude_IsLessThanDivisor(string left, string right)
{
SignificantNumber divisor = Parse(right);
SignificantNumber result = Parse(left) % divisor;

Assert.IsLessThan(SignificantNumber.Abs(divisor).Value, SignificantNumber.Abs(result).Value, $"{left} % {right} = {result}");
}
}
21 changes: 14 additions & 7 deletions SignificantNumber/SignificantNumber.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ namespace ktsu.SignificantNumber;
/// zero, so an uninitialized field or array element is a valid number.
/// </para>
/// <para>
/// Addition and subtraction round the result to the fewest decimal places among the operands. Multiplication,
/// division, and modulus round it to the fewest significant digits. An operand of exactly -1, 0, or 1 is treated as
/// having unlimited precision, so it never limits the result.
/// Addition, subtraction, and modulus round the result to the fewest decimal places among the operands.
/// Multiplication and division round it to the fewest significant digits. An operand of exactly -1, 0, or 1 is treated
/// as having unlimited precision, so it never limits the result.
/// </para>
/// </remarks>
[DebuggerDisplay("{Significand}e{Exponent}")]
Expand Down Expand Up @@ -247,12 +247,19 @@ public static SignificantNumber Divide(PreciseNumber left, PreciseNumber right)
/// </summary>
/// <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>
/// <returns>The modulus of the two numbers, whose magnitude is always less than that of <paramref name="right"/>.</returns>
/// <remarks>
/// A remainder is a subtraction, <c>left - right 脳 quotient</c>, so it is rounded to the fewest decimal places like
/// <see cref="Subtract(PreciseNumber, PreciseNumber)"/>. When that rounding carries the remainder up to the divisor
/// or beyond, the remainder is indistinguishable from a whole multiple of the divisor, so the result is zero.
/// </remarks>
public static SignificantNumber Mod(PreciseNumber left, PreciseNumber right)
{
int lowestSignificantDigits = LowestSignificantDigits(left, right);
return PreciseNumber.Mod(left, right)
.ToSignificantNumber(lowestSignificantDigits);
int lowestDecimalDigits = LowestDecimalDigits(left, right);
PreciseNumber remainder = PreciseNumber.Mod(left, right).Round(lowestDecimalDigits);
return PreciseNumber.Abs(remainder) >= PreciseNumber.Abs(right)
? Zero
: new(remainder);
}

/// <summary>
Expand Down
Loading