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 Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="ktsu.PreciseNumber" Version="2.0.1" />
<PackageVersion Include="ktsu.PreciseNumber" Version="2.0.3" />
<PackageVersion Include="Polyfill" Version="11.3.0" />
</ItemGroup>
</Project>
226 changes: 121 additions & 105 deletions README.md

Large diffs are not rendered by default.

50 changes: 22 additions & 28 deletions SignificantNumber.Test/SignificantNumberTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -420,14 +420,11 @@ public class DummyTestClass : ITest<int> { }
public class DummyNonTestClass { }

[TestMethod]
public void CreateFromComponents_WithExplicitNormalize_ReturnsNormalizedValue()
public void CreateFromComponents_ReturnsNormalizedValue()
{
// Tests the CreateFromComponents method with the normalize parameter
SignificantNumber number = SignificantNumber.CreateFromComponents(2, new BigInteger(123), true);
// Tests that CreateFromComponents keeps a significand with no trailing zeros as it is
SignificantNumber number = SignificantNumber.CreateFromComponents(2, new BigInteger(123));

// Verify it returns a normalized value (the implementation details might vary)
Assert.IsNotNull(number);
// This assumes normalization doesn't change the value for this simple case
Assert.AreEqual(new BigInteger(123), number.Significand);
Assert.AreEqual(2, number.Exponent);
}
Expand All @@ -439,10 +436,9 @@ public void Exp_NegativeValue_ReturnsCorrectResult()
SignificantNumber power = SignificantNumber.CreateFromComponents(0, new BigInteger(-2));
SignificantNumber result = SignificantNumber.Exp(power);

// The expected result should be approximately 1/e^2
// This is a very simple test; actual implementation may have more precision considerations
Assert.IsNotNull(result);
// Add specific value assertion based on your implementation
// The expected result is approximately 1/e^2, which is between zero and one
Assert.IsTrue(SignificantNumber.IsPositive(result), "Exp of a negative power should be positive");
Assert.IsLessThan(1.0, result.To<double>());
}

[TestMethod]
Expand Down Expand Up @@ -538,11 +534,19 @@ public void Operator_Comparison_WithDifferentTypes_ReturnsCorrectResults()
}

[TestMethod]
public void ToSignificantNumber_ReturnsSameInstance()
public void ToSignificantNumber_FromPreciseNumber_KeepsValue()
{
PreciseNumber number = SignificantNumber.CreateFromComponents(0, new BigInteger(5));
SignificantNumber result = number.ToSignificantNumber();
Assert.AreSame(number, result);
Assert.AreEqual<PreciseNumber>(number, result);
}

[TestMethod]
public void ToSignificantNumber_FromSignificantNumber_ReturnsSameValue()
{
SignificantNumber number = SignificantNumber.CreateFromComponents(0, new BigInteger(5));
SignificantNumber result = number.ToSignificantNumber();
Assert.AreEqual(number, result);
}

[TestMethod]
Expand Down Expand Up @@ -647,7 +651,7 @@ public void Parse_ValidString_ReturnsCorrectNumber()
public void TryParse_ValidString_ReturnsTrueAndCorrectNumber()
{
string input = "5";
bool success = SignificantNumber.TryParse(input, CultureInfo.InvariantCulture, out SignificantNumber? result);
bool success = SignificantNumber.TryParse(input, CultureInfo.InvariantCulture, out SignificantNumber result);
Assert.IsTrue(success, "TryParse should return true for a valid numeric string");
Assert.AreEqual(SignificantNumber.CreateFromComponents(0, new BigInteger(5)), result);
}
Expand All @@ -656,31 +660,21 @@ public void TryParse_ValidString_ReturnsTrueAndCorrectNumber()
public void TryParse_InvalidString_ReturnsFalse()
{
string input = "invalid";
bool success = SignificantNumber.TryParse(input, CultureInfo.InvariantCulture, out SignificantNumber? result);
bool success = SignificantNumber.TryParse(input, CultureInfo.InvariantCulture, out SignificantNumber result);
Assert.IsFalse(success, "TryParse should return false for an invalid string");
Assert.IsNull(result);
Assert.AreEqual(SignificantNumber.Zero, result);
}

[TestMethod]
public void CreateFromComponents_WithSanitizeTrue_RemovesTrailingZeros()
public void CreateFromComponents_TrailingZeros_AreRemoved()
{
SignificantNumber number = SignificantNumber.CreateFromComponents(2, new BigInteger(12300), true);
SignificantNumber number = SignificantNumber.CreateFromComponents(2, new BigInteger(12300));

// Assuming sanitization removes trailing zeros
// Sanitization removes trailing zeros and moves them into the exponent
Assert.AreEqual(new BigInteger(123), number.Significand);
Assert.AreEqual(4, number.Exponent); // Adjusted exponent
}

[TestMethod]
public void CreateFromComponents_WithSanitizeFalse_KeepsTrailingZeros()
{
SignificantNumber number = SignificantNumber.CreateFromComponents(2, new BigInteger(12300), false);

// Trailing zeros should remain
Assert.AreEqual(new BigInteger(12300), number.Significand);
Assert.AreEqual(2, number.Exponent);
}

[TestMethod]
public void DoesImplementGenericInterface_InvalidGenericInterface_ThrowsArgumentException()
{
Expand Down
234 changes: 234 additions & 0 deletions SignificantNumber.Test/SignificantNumberValueTypeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
// Copyright (c) 2023-2026 ktsu-dev contributors

namespace SignificantNumber.Test;

using System.Globalization;
using System.Numerics;
using System.Text;
using ktsu.PreciseNumber;
using ktsu.SignificantNumber;

/// <summary>
/// Covers what changed when <see cref="SignificantNumber"/> became a value type that holds a <see cref="PreciseNumber"/>.
/// </summary>
[TestClass]
public class SignificantNumberValueTypeTests
{
private static SignificantNumber Parse(string text) =>
SignificantNumber.Parse(text, NumberStyles.Float, CultureInfo.InvariantCulture);

// The generic helpers call conversions the way generic numeric code does, through the static interface members.
private static TTo ConvertChecked<TFrom, TTo>(TFrom value)
where TFrom : INumberBase<TFrom>
where TTo : INumberBase<TTo> =>
TTo.CreateChecked(value);

private static TTo ConvertSaturating<TFrom, TTo>(TFrom value)
where TFrom : INumberBase<TFrom>
where TTo : INumberBase<TTo> =>
TTo.CreateSaturating(value);

private static TTo ConvertTruncating<TFrom, TTo>(TFrom value)
where TFrom : INumberBase<TFrom>
where TTo : INumberBase<TTo> =>
TTo.CreateTruncating(value);

private static T Sum<T>(params T[] values)
where T : INumber<T>
{
T total = T.Zero;
foreach (T value in values)
{
total += value;
}

return total;
}

[TestMethod]
public void Default_EqualsZero()
{
SignificantNumber value = default;

Assert.AreEqual(SignificantNumber.Zero, value);
Assert.IsTrue(SignificantNumber.IsZero(value), "default should be zero");
Assert.AreEqual(0, value.Exponent);
Assert.AreEqual(BigInteger.Zero, value.Significand);
Assert.AreEqual(0, value.SignificantDigits);
Assert.AreEqual(Parse("0").GetHashCode(), value.GetHashCode());
}

[TestMethod]
public void Default_ArrayElementIsUsableZero()
{
SignificantNumber[] values = new SignificantNumber[3];

Assert.AreEqual(SignificantNumber.Zero, values[1]);
Assert.AreEqual(Parse("2.5"), values[1] + Parse("2.5"));
}

[TestMethod]
public void ImplicitConversion_ToPreciseNumber_KeepsValue()
{
SignificantNumber significant = Parse("123.45");
PreciseNumber precise = significant;

Assert.AreEqual(significant.Value, precise);
Assert.AreEqual(PreciseNumber.Parse("123.45", CultureInfo.InvariantCulture), precise);
}

[TestMethod]
public void ExplicitConversion_FromPreciseNumber_HoldsValue()
{
PreciseNumber precise = PreciseNumber.Parse("123.45", CultureInfo.InvariantCulture);
SignificantNumber significant = (SignificantNumber)precise;

Assert.AreEqual(precise, significant.Value);
Assert.AreEqual(significant, SignificantNumber.FromPreciseNumber(precise));
}

[TestMethod]
public void ToPreciseNumber_ReturnsHeldValue()
{
SignificantNumber significant = Parse("0.001");

Assert.AreEqual(significant.Value, significant.ToPreciseNumber());
}

[TestMethod]
public void CreateChecked_FromDouble_IsExact()
{
SignificantNumber result = ConvertChecked<double, SignificantNumber>(0.3048);

Assert.AreEqual(Parse("0.3048"), result);
}

[TestMethod]
public void CreateChecked_FromInt_IsExact()
{
SignificantNumber result = ConvertChecked<int, SignificantNumber>(42);

Assert.AreEqual(Parse("42"), result);
}

[TestMethod]
public void CreateChecked_ToDouble_IsExact()
{
double result = ConvertChecked<SignificantNumber, double>(Parse("0.3048"));

Assert.AreEqual(0.3048, result);
}

[TestMethod]
public void CreateChecked_ToAndFromPreciseNumber_KeepsValue()
{
SignificantNumber significant = Parse("98.6");

PreciseNumber precise = ConvertChecked<SignificantNumber, PreciseNumber>(significant);
SignificantNumber roundTripped = ConvertChecked<PreciseNumber, SignificantNumber>(precise);

Assert.AreEqual(significant.Value, precise);
Assert.AreEqual(significant, roundTripped);
}

[TestMethod]
public void CreateChecked_ToInt_OutOfRange_Throws()
{
SignificantNumber tooLarge = Parse("1e20");

Assert.ThrowsExactly<OverflowException>(() => ConvertChecked<SignificantNumber, int>(tooLarge));
}

[TestMethod]
public void CreateSaturating_ToByte_Clamps()
{
byte result = ConvertSaturating<SignificantNumber, byte>(Parse("300"));

Assert.AreEqual(byte.MaxValue, result);
}

[TestMethod]
public void CreateTruncating_ToInt_TruncatesTowardZero()
{
int result = ConvertTruncating<SignificantNumber, int>(Parse("12.9"));

Assert.AreEqual(12, result);
}

[TestMethod]
public void GenericSum_AppliesDecimalPlaceRule()
{
// 1.25 + 2.1 is 3.35, which the decimal place rule rounds to one decimal place. Zero, the starting total,
// has unlimited precision, so it doesn't limit the first addition.
SignificantNumber result = Sum(Parse("1.25"), Parse("2.1"));

Assert.AreEqual(Parse("3.4"), result);
}

[TestMethod]
public void StaticMaxMinClamp_CompareByValue()
{
SignificantNumber low = Parse("1.5");
SignificantNumber high = Parse("2.5");

Assert.AreEqual(high, SignificantNumber.Max(low, high));
Assert.AreEqual(low, SignificantNumber.Min(low, high));
Assert.AreEqual(high, SignificantNumber.Clamp(Parse("9"), low, high));
Assert.AreEqual(Parse("1.2"), SignificantNumber.Round(Parse("1.23"), 1));
}

[TestMethod]
public void TryParse_Invalid_YieldsZero()
{
bool parsed = SignificantNumber.TryParse("not a number", NumberStyles.Float, CultureInfo.InvariantCulture, out SignificantNumber result);

Assert.IsFalse(parsed, "TryParse should fail for text that isn't a number");
Assert.AreEqual(SignificantNumber.Zero, result);
}

[TestMethod]
public void CompareToObject_Null_SortsFirst()
{
object? nothing = null;
int result = Parse("5").CompareTo(nothing);

Assert.AreEqual(1, result);
}

[TestMethod]
public void CompareToObject_BoxedPreciseNumber_ComparesValue()
{
object precise = PreciseNumber.Parse("5", CultureInfo.InvariantCulture);

Assert.AreEqual(0, Parse("5").CompareTo(precise));
}

[TestMethod]
public void ToString_MatchesHeldValue()
{
SignificantNumber significant = Parse("123.45");

Assert.AreEqual("123.45", significant.ToString(CultureInfo.InvariantCulture));
Assert.AreEqual(significant.Value.ToString(CultureInfo.InvariantCulture), significant.ToString(CultureInfo.InvariantCulture));
}

[TestMethod]
public void Utf8TryFormat_WritesSameTextAsToString()
{
SignificantNumber significant = Parse("123.45");
Span<byte> buffer = stackalloc byte[64];

bool formatted = ((IUtf8SpanFormattable)significant).TryFormat(buffer, out int bytesWritten, default, CultureInfo.InvariantCulture);

Assert.IsTrue(formatted, "Formatting a short number into a 64 byte buffer should succeed");
Assert.AreEqual(significant.ToString(CultureInfo.InvariantCulture), Encoding.UTF8.GetString(buffer[..bytesWritten]));
}

[TestMethod]
public void ToSignificantNumberWithDigits_FromSignificantNumber_ReducesSignificance()
{
SignificantNumber result = Parse("123.456").ToSignificantNumber(3);

Assert.AreEqual(Parse("123"), result);
}
}
Loading