Skip to content

Commit

Permalink
Implement Parsing.
Browse files Browse the repository at this point in the history
  • Loading branch information
Corniel committed Jan 24, 2024
1 parent ec8aba8 commit 533b637
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 28 deletions.
14 changes: 14 additions & 0 deletions specs/Qowaiv.Specs/Financial/Amount_INumber_specs.cs
Expand Up @@ -164,6 +164,20 @@ public void min_maginiute_equal_to_decimal([Random(Min, Max, 3)] double x, [Rand
Number.MinMagnitudeNumber((Amount)x_, (Amount)y_).Should().Be((Amount)Number.MinMagnitudeNumber(x_, y_));
}

[Test]
public void multiplication_is_not_supported()
{
var multiply = () => Number.Multiply(6.Amount(), 7.Amount());
multiply.Should().Throw<NotSupportedException>(because: "Multiplying amounts makes no sense.");
}

[Test]
public void division_is_not_supported()
{
var multiply = () => Number.Divide(6.Amount(), 7.Amount());
multiply.Should().Throw<NotSupportedException>(because: "Devision amounts makes no sense (when the result is amount).");
}

private const double Min = -79228162514264337593543950335d;
private const double Max = +79228162514264337593543950335d;
private const int Count = 8;
Expand Down
16 changes: 16 additions & 0 deletions specs/Qowaiv.Specs/Financial/Amount_specs.cs
Expand Up @@ -32,6 +32,22 @@ public void Max_of_collection_returns_maximum()
=> Amount.Max(7.Amount(), 17.Amount(), -48.Amount()).Should().Be(17.Amount());
}

public class Can_not_be_parsed
{
[TestCase(NumberStyles.Number)]
[TestCase(NumberStyles.Integer)]
public void strings_with_currency_if_style_does_not_allow_currency_sign(NumberStyles style)
=> Amount.TryParse("42 EUR", style, CultureInfo.InvariantCulture, out _)
.Should().BeFalse();

[TestCase(NumberStyles.HexNumber)]
[TestCase(NumberStyles.AllowExponent)]
public void using_a_number_style_other_then_Curency(NumberStyles style)
=> style.Invoking(s => Amount.TryParse("4.50", s, CultureInfo.InvariantCulture, out _))
.Should().Throw<ArgumentOutOfRangeException>()
.WithMessage("The number style '*' is not supported.*");
}

public class Is_comparable
{
[Test]
Expand Down
10 changes: 10 additions & 0 deletions src/Qowaiv.TestTools/Numerics/Number.cs
Expand Up @@ -116,6 +116,16 @@ public static class Number
public static bool IsImaginaryNumber<T>(T number) where T : INumberBase<T>
=> T.IsImaginaryNumber(number);

/// <summary>Defines a mechanism for computing the product of two values.</summary>
[Pure]
public static T Multiply<T>(T x, T y) where T : IMultiplyOperators<T, T, T>
=> x * y;

/// <summary>Divides two values together to compute their quotient.</summary>
[Pure]
public static T Divide<T>(T x, T y) where T : IDivisionOperators<T, T, T>
=> x / y;

/// <inheritdoc cref="INumberBase{T}.MaxMagnitude(T, T)" />
[Pure]
public static T MaxMagnitude<T>(T x, T y) where T : INumberBase<T>
Expand Down
46 changes: 24 additions & 22 deletions src/Qowaiv/Financial/Amount.INumber.cs
Expand Up @@ -231,37 +231,39 @@ bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, Re
return false;
}

/// <inheritdoc />
[Pure]
[ExcludeFromCodeCoverage(Justification = "Only explicitly exposed overload of thoroughly tested parsing method.")]
static Amount INumberBase<Amount>.Parse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider)
{
throw new NotImplementedException();
}
=> TryParse(s.ToString(), style, provider, out var svo)
? svo
: throw Unparsable.ForValue<Amount>(s.ToString(), QowaivMessages.FormatExceptionPercentage);

/// <inheritdoc />
[Pure]
[ExcludeFromCodeCoverage(Justification = "Only explicitly exposed overload of thoroughly tested parsing method.")]
static Amount INumberBase<Amount>.Parse(string s, NumberStyles style, IFormatProvider? provider)
{
throw new NotImplementedException();
}
=> TryParse(s, style, provider, out var svo)
? svo
: throw Unparsable.ForValue<Amount>(s, QowaivMessages.FormatExceptionPercentage);

/// <inheritdoc />
[Pure]
[ExcludeFromCodeCoverage(Justification = "Only explicitly exposed overload of thoroughly tested parsing method.")]
static Amount ISpanParsable<Amount>.Parse(ReadOnlySpan<char> s, IFormatProvider? provider)
{
throw new NotImplementedException();
}
=> TryParse(s.ToString(), NumberStyles.Currency, provider, out var svo)
? svo
: throw Unparsable.ForValue<Amount>(s.ToString(), QowaivMessages.FormatExceptionPercentage);

/// <inheritdoc />
[ExcludeFromCodeCoverage(Justification = "Only explicitly exposed overload of thoroughly tested parsing method.")]
static bool INumberBase<Amount>.TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out Amount result)
{
throw new NotImplementedException();
}

static bool INumberBase<Amount>.TryParse(string? s, NumberStyles style, IFormatProvider? provider, out Amount result)
{
throw new NotImplementedException();
}
=> TryParse(s.ToString(), style, provider, out result);

/// <inheritdoc />
[ExcludeFromCodeCoverage(Justification = "Only explicitly exposed overload of thoroughly tested parsing method.")]
static bool ISpanParsable<Amount>.TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, out Amount result)
{
throw new NotImplementedException();
}


=> TryParse(s.ToString(), NumberStyles.Currency, provider, out result);

static Amount IMultiplyOperators<Amount, Amount, Amount>.operator *(Amount left, Amount right)
=> throw new NotSupportedException();
Expand Down
67 changes: 61 additions & 6 deletions src/Qowaiv/Financial/Amount.cs
Expand Up @@ -130,7 +130,7 @@ namespace Qowaiv.Financial;
/// enough to have a <see cref="string"/> representation of -0.
/// </remarks>
[Pure]
public double ToJson() => m_Value == decimal.Zero ? 0 : (double)m_Value;
public double ToJson() => (double)m_Value;

/// <summary>Returns a <see cref="string"/> that represents the current Amount for debug purposes.</summary>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
Expand Down Expand Up @@ -207,14 +207,69 @@ public string ToString(string? format, IFormatProvider? formatProvider)
/// True if the string was converted successfully, otherwise false.
/// </returns>
public static bool TryParse(string? s, IFormatProvider? provider, out Amount result)
=> TryParse(s, NumberStyles.Currency, provider, out result);

/// <summary>Converts the string to an amount.
/// A return value indicates whether the conversion succeeded.
/// </summary>
/// <param name="s">
/// A string containing an Amount to convert.
/// </param>
/// <param name="style">
/// The preferred number style.
/// </param>
/// <param name="provider">
/// The specified format provider.
/// </param>
/// <param name="result">
/// The result of the parsing.
/// </param>
/// <returns>
/// True if the string was converted successfully, otherwise false.
/// </returns>
public static bool TryParse(string? s, NumberStyles style, IFormatProvider? provider, out Amount result)
{
result = default;
if (Money.TryParse(s, provider, out Money money))
Guard(style);

return style.HasFlag(NumberStyles.AllowCurrencySymbol)
? ParseMoney(s, provider, out result)
: ParseAmount(s, style,provider, out result);

static bool ParseMoney(string? s, IFormatProvider? provider, out Amount result)
{
if (Money.TryParse(s, provider, out Money money))
{
result = money.Amount;
return true;
}
else
{
result = default;
return false;
}
}
static bool ParseAmount(string? s, NumberStyles style, IFormatProvider? provider, out Amount result)
{
if (decimal.TryParse(s, style, provider, out decimal amount))
{
result = new(amount);
return true;
}
else
{
result = default;
return false;
}
}

static void Guard(NumberStyles style)
{
result = (Amount)(decimal)money;
return true;
var extra = style & ~NumberStyles.Currency;
if (extra != NumberStyles.None)
{
throw new ArgumentOutOfRangeException(nameof(style), string.Format(QowaivMessages.ArgumentOutOfRange_NumberStyleNotSupported, extra));
}
}
return false;
}

/// <summary>Creates an Amount from a Decimal.</summary >
Expand Down

0 comments on commit 533b637

Please sign in to comment.