diff --git a/SignificantNumber.Test/SignificantNumberExactOperandTests.cs b/SignificantNumber.Test/SignificantNumberExactOperandTests.cs
new file mode 100644
index 0000000..a129541
--- /dev/null
+++ b/SignificantNumber.Test/SignificantNumberExactOperandTests.cs
@@ -0,0 +1,100 @@
+// Copyright (c) 2023-2026 ktsu-dev contributors
+
+namespace SignificantNumber.Test;
+
+using System.Globalization;
+using System.Numerics;
+using ktsu.SignificantNumber;
+
+///
+/// Covers arithmetic and comparison where both operands are -1, 0, or 1, which have unlimited precision.
+///
+[TestClass]
+public class SignificantNumberExactOperandTests
+{
+ private static SignificantNumber Parse(string text) =>
+ SignificantNumber.Parse(text, NumberStyles.Float, CultureInfo.InvariantCulture);
+
+ private static T Square(T value)
+ where T : INumber =>
+ value * value;
+
+ [TestMethod]
+ [DataRow("1", "0")]
+ [DataRow("-1", "0")]
+ [DataRow("0", "0")]
+ [DataRow("0", "1")]
+ [DataRow("0", "-1")]
+ public void Multiply_ByZero_ReturnsZero(string left, string right) =>
+ Assert.AreEqual(SignificantNumber.Zero, Parse(left) * Parse(right));
+
+ [TestMethod]
+ public void Multiply_DefaultByDefault_ReturnsZero() =>
+ Assert.AreEqual(SignificantNumber.Zero, default(SignificantNumber) * default(SignificantNumber));
+
+ [TestMethod]
+ public void Square_OfZeroThroughGenericMath_ReturnsZero() =>
+ Assert.AreEqual(SignificantNumber.Zero, Square(SignificantNumber.Zero));
+
+ [TestMethod]
+ public void Multiply_ExactOperands_IsCommutativeAndExact()
+ {
+ string[] values = ["-1", "0", "1"];
+ foreach (string left in values)
+ {
+ foreach (string right in values)
+ {
+ SignificantNumber expected = Parse((int.Parse(left, CultureInfo.InvariantCulture) * int.Parse(right, CultureInfo.InvariantCulture)).ToString(CultureInfo.InvariantCulture));
+ Assert.AreEqual(expected, Parse(left) * Parse(right), $"{left} * {right}");
+ Assert.AreEqual(Parse(right) * Parse(left), Parse(left) * Parse(right), $"{left} * {right} vs {right} * {left}");
+ }
+ }
+ }
+
+ [TestMethod]
+ [DataRow("0", "1", "0")]
+ [DataRow("0", "-1", "0")]
+ [DataRow("1", "-1", "-1")]
+ [DataRow("-1", "-1", "1")]
+ public void Divide_ExactOperands_ReturnsExactQuotient(string left, string right, string expected) =>
+ Assert.AreEqual(Parse(expected), Parse(left) / Parse(right));
+
+ [TestMethod]
+ [DataRow("0", "1")]
+ [DataRow("1", "1")]
+ [DataRow("-1", "1")]
+ [DataRow("0", "-1")]
+ public void Mod_ExactOperands_ReturnsZero(string left, string right) =>
+ Assert.AreEqual(SignificantNumber.Zero, Parse(left) % Parse(right));
+
+ [TestMethod]
+ public void CompareTo_ExactOperands_IsAntisymmetric()
+ {
+ string[] values = ["-1", "0", "1"];
+ foreach (string left in values)
+ {
+ foreach (string right in values)
+ {
+ int expected = int.Parse(left, CultureInfo.InvariantCulture).CompareTo(int.Parse(right, CultureInfo.InvariantCulture));
+ Assert.AreEqual(expected, int.Sign(Parse(left).CompareTo(Parse(right))), $"{left}.CompareTo({right})");
+ }
+ }
+ }
+
+ [TestMethod]
+ [DataRow("1", "1.23", "1.23")]
+ [DataRow("-1", "1.23", "-1.23")]
+ [DataRow("1.23", "1", "1.23")]
+ [DataRow("1.23", "-1", "-1.23")]
+ [DataRow("0", "1.23", "0")]
+ [DataRow("1.23", "0", "0")]
+ public void Multiply_OneExactOperand_KeepsTheOtherOperandsPrecision(string left, string right, string expected) =>
+ Assert.AreEqual(Parse(expected), Parse(left) * Parse(right));
+
+ [TestMethod]
+ [DataRow("1.23", "1", "1.23")]
+ [DataRow("1.23", "-1", "-1.23")]
+ [DataRow("1", "4.1", "0.24")]
+ public void Divide_OneExactOperand_KeepsTheOtherOperandsPrecision(string left, string right, string expected) =>
+ Assert.AreEqual(Parse(expected), Parse(left) / Parse(right));
+}
diff --git a/SignificantNumber/SignificantNumber.cs b/SignificantNumber/SignificantNumber.cs
index 5ca3bf0..7b43529 100644
--- a/SignificantNumber/SignificantNumber.cs
+++ b/SignificantNumber/SignificantNumber.cs
@@ -178,18 +178,31 @@ private static int LowestDecimalDigits(PreciseNumber left, PreciseNumber right)
///
/// The first number.
/// The second number.
- /// The lower of the significant digit counts of the two numbers.
+ ///
+ /// The lower of the significant digit counts of the two numbers, or 1 when both have unlimited precision, since any
+ /// product, quotient, or remainder of -1, 0, and 1 is exact at one significant digit.
+ ///
private static int LowestSignificantDigits(PreciseNumber left, PreciseNumber right)
{
- int leftSignificantDigits = left.SignificantDigits;
- int rightSignificantDigits = right.SignificantDigits;
+ bool leftIsExact = HasInfinitePrecision(left);
+ bool rightIsExact = HasInfinitePrecision(right);
+
+ if (leftIsExact && rightIsExact)
+ {
+ return 1;
+ }
- leftSignificantDigits = HasInfinitePrecision(left) ? rightSignificantDigits : leftSignificantDigits;
- rightSignificantDigits = HasInfinitePrecision(right) ? leftSignificantDigits : rightSignificantDigits;
+ if (leftIsExact)
+ {
+ return right.SignificantDigits;
+ }
+
+ if (rightIsExact)
+ {
+ return left.SignificantDigits;
+ }
- return leftSignificantDigits < rightSignificantDigits
- ? leftSignificantDigits
- : rightSignificantDigits;
+ return int.Min(left.SignificantDigits, right.SignificantDigits);
}
///