diff --git a/src/FixedPointNano/FixedPointNano.cs b/src/FixedPointNano/FixedPointNano.cs index 42a5c4f..db16af6 100644 --- a/src/FixedPointNano/FixedPointNano.cs +++ b/src/FixedPointNano/FixedPointNano.cs @@ -33,7 +33,10 @@ namespace Seerstone; IUnaryPlusOperators, IIncrementOperators, IDecrementOperators, - IComparisonOperators + IComparisonOperators, + IParsable, + ISpanParsable, + IMinMaxValue { /// The number of decimal places supported by . public const int DecimalPlaces = 9; diff --git a/tests/FixedPointNano.Tests/FixedPointNanoTests.cs b/tests/FixedPointNano.Tests/FixedPointNanoTests.cs index 34d155c..2795000 100644 --- a/tests/FixedPointNano.Tests/FixedPointNanoTests.cs +++ b/tests/FixedPointNano.Tests/FixedPointNanoTests.cs @@ -479,4 +479,130 @@ public void GenericMathInterfacesShouldProvideIdentities() static TResult GetAdditiveIdentity() where T : IAdditiveIdentity => T.AdditiveIdentity; static TResult GetMultiplicativeIdentity() where T : IMultiplicativeIdentity => T.MultiplicativeIdentity; } + + [Test] + public void ParseShouldRoundtripFormattedValues() + { + Assert.Multiple(() => + { + Assert.That(FixedPointNano.Parse("1.5", null), Is.EqualTo(FixedPointNano.FromDecimal(1.5m))); + Assert.That(FixedPointNano.Parse("0", null), Is.EqualTo(FixedPointNano.Zero)); + Assert.That(FixedPointNano.Parse("-3.141592653", null), Is.EqualTo(FixedPointNano.FromDecimal(-3.141592653m))); + Assert.That(FixedPointNano.Parse("1000000000.123456789", null), Is.EqualTo(FixedPointNano.FromDecimal(1000000000.123456789m))); + }); + } + + [Test] + public void ParseSpanShouldRoundtripFormattedValues() + { + Assert.Multiple(() => + { + Assert.That(FixedPointNano.Parse("1.5".AsSpan(), null), Is.EqualTo(FixedPointNano.FromDecimal(1.5m))); + Assert.That(FixedPointNano.Parse("0".AsSpan(), null), Is.EqualTo(FixedPointNano.Zero)); + Assert.That(FixedPointNano.Parse("-42.000000001".AsSpan(), null), Is.EqualTo(FixedPointNano.FromDecimal(-42.000000001m))); + }); + } + + [Test] + public void ParseShouldThrowOnInvalidInput() + { + Assert.Throws(() => FixedPointNano.Parse("not-a-number", null)); + Assert.Throws(() => FixedPointNano.Parse("", null)); + Assert.Throws(() => FixedPointNano.Parse("1.2.3", null)); + } + + [Test] + public void TryParseShouldReturnTrueForValidInput() + { + Assert.Multiple(() => + { + Assert.That(FixedPointNano.TryParse("2.718281828", null, out var result1), Is.True); + Assert.That(result1, Is.EqualTo(FixedPointNano.FromDecimal(2.718281828m))); + + Assert.That(FixedPointNano.TryParse("-1", null, out var result2), Is.True); + Assert.That(result2, Is.EqualTo(FixedPointNano.NegativeOne)); + + Assert.That(FixedPointNano.TryParse("0.000000001", null, out var result3), Is.True); + Assert.That(result3, Is.EqualTo(FixedPointNano.Epsilon)); + }); + } + + [Test] + public void TryParseShouldReturnFalseForInvalidInput() + { + Assert.Multiple(() => + { + Assert.That(FixedPointNano.TryParse((string?)null, null, out _), Is.False); + Assert.That(FixedPointNano.TryParse("not-a-number", null, out _), Is.False); + Assert.That(FixedPointNano.TryParse("", null, out _), Is.False); + }); + } + + [Test] + public void TryParseSpanShouldWorkCorrectly() + { + Assert.Multiple(() => + { + Assert.That(FixedPointNano.TryParse("1.23456789".AsSpan(), null, out var result1), Is.True); + Assert.That(result1, Is.EqualTo(FixedPointNano.FromDecimal(1.23456789m))); + + Assert.That(FixedPointNano.TryParse("abc".AsSpan(), null, out _), Is.False); + Assert.That(FixedPointNano.TryParse(ReadOnlySpan.Empty, null, out _), Is.False); + }); + } + + [Test] + public void TryParseConvenienceOverloadsShouldWork() + { + Assert.Multiple(() => + { + Assert.That(FixedPointNano.TryParse("5.5", out var result1), Is.True); + Assert.That(result1, Is.EqualTo(FixedPointNano.FromDecimal(5.5m))); + + Assert.That(FixedPointNano.TryParse("5.5".AsSpan(), out var result2), Is.True); + Assert.That(result2, Is.EqualTo(FixedPointNano.FromDecimal(5.5m))); + }); + } + + [Test] + public void ParseShouldUseFormatProviderForCultureSpecificInput() + { + var deDE = CultureInfo.GetCultureInfo("de-DE"); + + Assert.That(FixedPointNano.TryParse("1,5", deDE, out var result), Is.True); + Assert.That(result, Is.EqualTo(FixedPointNano.FromDecimal(1.5m))); + } + + [Test] + public void IParsableInterfaceShouldWork() + { + var parsed = ParseViaInterface("3.14", null); + Assert.That(parsed, Is.EqualTo(FixedPointNano.FromDecimal(3.14m))); + + static T ParseViaInterface(string s, IFormatProvider? provider) where T : IParsable + => T.Parse(s, provider); + } + + [Test] + public void ISpanParsableInterfaceShouldWork() + { + var parsed = ParseSpanViaInterface("2.71828".AsSpan(), null); + Assert.That(parsed, Is.EqualTo(FixedPointNano.FromDecimal(2.71828m))); + + static T ParseSpanViaInterface(ReadOnlySpan s, IFormatProvider? provider) where T : ISpanParsable + => T.Parse(s, provider); + } + + [Test] + public void IMinMaxValueInterfaceShouldExposeCorrectBounds() + { + var maxValue = GetMaxValue(); + var minValue = GetMinValue(); + + Assert.That(maxValue, Is.EqualTo(FixedPointNano.MaxValue)); + Assert.That(minValue, Is.EqualTo(FixedPointNano.MinValue)); + + static T GetMaxValue() where T : IMinMaxValue => T.MaxValue; + static T GetMinValue() where T : IMinMaxValue => T.MinValue; + } }