-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Finish genericity and keep the tests at decimal so far.
- Loading branch information
1 parent
5f4c5df
commit c758d0a
Showing
29 changed files
with
362 additions
and
308 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
namespace Funcky.Test; | ||
|
||
public record SwissMoney(Money Get) | ||
public record SwissMoney(Money<decimal> Get) | ||
{ | ||
public const decimal SmallestCoin = 0.05m; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,85 +1,87 @@ | ||
using System.Numerics; | ||
using Funcky.Extensions; | ||
using Funcky.Monads; | ||
|
||
namespace Funcky; | ||
|
||
internal class DefaultDistributionStrategy : IDistributionStrategy | ||
internal class DefaultDistributionStrategy<TUnderlyingType> : IDistributionStrategy<TUnderlyingType> | ||
where TUnderlyingType : IFloatingPoint<TUnderlyingType> | ||
{ | ||
private readonly Option<MoneyEvaluationContext> _context; | ||
private readonly Option<MoneyEvaluationContext<TUnderlyingType>> _context; | ||
|
||
public DefaultDistributionStrategy(Option<MoneyEvaluationContext> context) | ||
public DefaultDistributionStrategy(Option<MoneyEvaluationContext<TUnderlyingType>> context) | ||
{ | ||
_context = context; | ||
} | ||
|
||
public Money Distribute(MoneyDistributionPart part, Money total) | ||
public Money<TUnderlyingType> Distribute(MoneyDistributionPart<TUnderlyingType> part, Money<TUnderlyingType> total) | ||
=> IsDistributable(part, total) | ||
? total with { Amount = SliceAmount(part, total), Currency = total.Currency } | ||
: throw new ImpossibleDistributionException($"It is impossible to distribute {ToDistribute(part, total)} in sizes of {Precision(part.Distribution, total)} with the current Rounding strategy: {RoundingStrategy(total)}."); | ||
|
||
private bool IsDistributable(MoneyDistributionPart part, Money money) | ||
private bool IsDistributable(MoneyDistributionPart<TUnderlyingType> part, Money<TUnderlyingType> money) | ||
=> RoundingStrategy(money).IsSameAfterRounding(Precision(part.Distribution, money)) | ||
&& RoundingStrategy(money).IsSameAfterRounding(ToDistribute(part, money)); | ||
|
||
private decimal SliceAmount(MoneyDistributionPart part, Money money) | ||
private TUnderlyingType SliceAmount(MoneyDistributionPart<TUnderlyingType> part, Money<TUnderlyingType> money) | ||
=> Slice(part.Distribution, part.Index, money) + DistributeRest(part, money); | ||
|
||
private decimal DistributeRest(MoneyDistributionPart part, Money money) | ||
private TUnderlyingType DistributeRest(MoneyDistributionPart<TUnderlyingType> part, Money<TUnderlyingType> money) | ||
=> part.Index switch | ||
{ | ||
_ when AtLeastOneDistributionUnitLeft(part, money) => SignedPrecision(part.Distribution, money), | ||
_ when BetweenZeroToOneDistributionUnitLeft(part, money) => ToDistribute(part, money) - AlreadyDistributed(part, money), | ||
_ => 0.0m, | ||
_ => TUnderlyingType.Zero, | ||
}; | ||
|
||
private bool AtLeastOneDistributionUnitLeft(MoneyDistributionPart part, Money money) | ||
=> Precision(part.Distribution, money) * (part.Index + 1) < Math.Abs(ToDistribute(part, money)); | ||
private bool AtLeastOneDistributionUnitLeft(MoneyDistributionPart<TUnderlyingType> part, Money<TUnderlyingType> money) | ||
=> Precision(part.Distribution, money) * TUnderlyingType.CreateChecked(part.Index + 1) < TUnderlyingType.Abs(ToDistribute(part, money)); | ||
|
||
private bool BetweenZeroToOneDistributionUnitLeft(MoneyDistributionPart part, Money money) | ||
=> Precision(part.Distribution, money) * part.Index < Math.Abs(ToDistribute(part, money)); | ||
private bool BetweenZeroToOneDistributionUnitLeft(MoneyDistributionPart<TUnderlyingType> part, Money<TUnderlyingType> money) | ||
=> Precision(part.Distribution, money) * TUnderlyingType.CreateChecked(part.Index) < TUnderlyingType.Abs(ToDistribute(part, money)); | ||
|
||
private decimal AlreadyDistributed(MoneyDistributionPart part, Money money) | ||
=> SignedPrecision(part.Distribution, money) * part.Index; | ||
private TUnderlyingType AlreadyDistributed(MoneyDistributionPart<TUnderlyingType> part, Money<TUnderlyingType> money) | ||
=> SignedPrecision(part.Distribution, money) * TUnderlyingType.CreateChecked(part.Index); | ||
|
||
private IRoundingStrategy<decimal> RoundingStrategy(Money money) | ||
private IRoundingStrategy<TUnderlyingType> RoundingStrategy(Money<TUnderlyingType> money) | ||
=> _context.Match( | ||
some: c => c.RoundingStrategy, | ||
none: money.RoundingStrategy); | ||
|
||
private decimal ToDistribute(MoneyDistributionPart part, Money money) | ||
private TUnderlyingType ToDistribute(MoneyDistributionPart<TUnderlyingType> part, Money<TUnderlyingType> money) | ||
=> money.Amount - DistributedTotal(part, money); | ||
|
||
private decimal DistributedTotal(MoneyDistributionPart part, Money money) | ||
private TUnderlyingType DistributedTotal(MoneyDistributionPart<TUnderlyingType> part, Money<TUnderlyingType> money) | ||
=> part | ||
.Distribution | ||
.Factors | ||
.WithIndex() | ||
.Sum(f => Slice(part.Distribution, f.Index, money)); | ||
.Aggregate(TUnderlyingType.Zero, (sum, value) => sum + Slice(part.Distribution, value.Index, money)); | ||
|
||
private decimal Slice(MoneyDistribution distribution, int index, Money money) | ||
private TUnderlyingType Slice(MoneyDistribution<TUnderlyingType> distribution, int index, Money<TUnderlyingType> money) | ||
=> Truncate(ExactSlice(distribution, index, money), Precision(distribution, money)); | ||
|
||
private static decimal ExactSlice(MoneyDistribution distribution, int index, Money money) | ||
=> money.Amount / DistributionTotal(distribution) * distribution.Factors[index]; | ||
private static TUnderlyingType ExactSlice(MoneyDistribution<TUnderlyingType> distribution, int index, Money<TUnderlyingType> money) | ||
=> money.Amount / TUnderlyingType.CreateChecked(DistributionTotal(distribution)) * TUnderlyingType.CreateChecked(distribution.Factors[index]); | ||
|
||
private decimal SignedPrecision(MoneyDistribution distribution, Money money) | ||
=> Precision(distribution, money).CopySign(money.Amount); | ||
private TUnderlyingType SignedPrecision(MoneyDistribution<TUnderlyingType> distribution, Money<TUnderlyingType> money) | ||
=> TUnderlyingType.CopySign(Precision(distribution, money), money.Amount); | ||
|
||
// Order of evaluation: Distribution > Context Distribution > Context Currency > Money Currency | ||
private decimal Precision(MoneyDistribution distribution, Money money) | ||
private TUnderlyingType Precision(MoneyDistribution<TUnderlyingType> distribution, Money<TUnderlyingType> money) | ||
=> distribution | ||
.Precision | ||
.OrElse(_context.AndThen(c => c.DistributionUnit)) | ||
.GetOrElse(Power<decimal>.OfATenth(MinorUnitDigits(money))); | ||
.GetOrElse(Power<TUnderlyingType>.OfATenth(MinorUnitDigits(money))); | ||
|
||
private int MinorUnitDigits(Money money) | ||
private int MinorUnitDigits(Money<TUnderlyingType> money) | ||
=> _context.Match( | ||
none: money.Currency.MinorUnitDigits, | ||
some: c => c.TargetCurrency.MinorUnitDigits); | ||
|
||
private static decimal Truncate(decimal amount, decimal precision) | ||
=> decimal.Truncate(amount / precision) * precision; | ||
private static TUnderlyingType Truncate(TUnderlyingType amount, TUnderlyingType precision) | ||
=> TUnderlyingType.Truncate(amount / precision) * precision; | ||
|
||
private static int DistributionTotal(MoneyDistribution distribution) | ||
private static int DistributionTotal(MoneyDistribution<TUnderlyingType> distribution) | ||
=> distribution.Factors.Sum(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,9 @@ | ||
using System.Numerics; | ||
|
||
namespace Funcky; | ||
|
||
internal interface IDistributionStrategy | ||
internal interface IDistributionStrategy<TUnderlyingType> | ||
where TUnderlyingType : IFloatingPoint<TUnderlyingType> | ||
{ | ||
Money Distribute(MoneyDistributionPart part, Money total); | ||
Money<TUnderlyingType> Distribute(MoneyDistributionPart<TUnderlyingType> part, Money<TUnderlyingType> total); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,33 +1,34 @@ | ||
using System.Numerics; | ||
|
||
namespace Funcky; | ||
|
||
public interface IMoneyExpression | ||
public interface IMoneyExpression<TUnderlyingType> | ||
where TUnderlyingType : IFloatingPoint<TUnderlyingType> | ||
{ | ||
#if DEFAULT_INTERFACE_IMPLEMENTATION_SUPPORTED | ||
public static IMoneyExpression operator *(IMoneyExpression multiplicand, decimal multiplier) | ||
public static IMoneyExpression<TUnderlyingType> operator *(IMoneyExpression<TUnderlyingType> multiplicand, TUnderlyingType multiplier) | ||
=> multiplicand.Multiply(multiplier); | ||
|
||
public static IMoneyExpression operator *(decimal multiplier, IMoneyExpression multiplicand) | ||
public static IMoneyExpression<TUnderlyingType> operator *(TUnderlyingType multiplier, IMoneyExpression<TUnderlyingType> multiplicand) | ||
=> multiplicand.Multiply(multiplier); | ||
|
||
public static IMoneyExpression operator /(IMoneyExpression dividend, decimal divisor) | ||
public static IMoneyExpression<TUnderlyingType> operator /(IMoneyExpression<TUnderlyingType> dividend, TUnderlyingType divisor) | ||
=> dividend.Divide(divisor); | ||
|
||
public static decimal operator /(IMoneyExpression dividend, IMoneyExpression divisor) | ||
public static TUnderlyingType operator /(IMoneyExpression<TUnderlyingType> dividend, IMoneyExpression<TUnderlyingType> divisor) | ||
=> dividend.Divide(divisor); | ||
|
||
public static IMoneyExpression operator +(IMoneyExpression augend, IMoneyExpression addend) | ||
public static IMoneyExpression<TUnderlyingType> operator +(IMoneyExpression<TUnderlyingType> augend, IMoneyExpression<TUnderlyingType> addend) | ||
=> augend.Add(addend); | ||
|
||
public static IMoneyExpression operator +(IMoneyExpression moneyExpression) | ||
public static IMoneyExpression<TUnderlyingType> operator +(IMoneyExpression<TUnderlyingType> moneyExpression) | ||
=> moneyExpression; | ||
|
||
public static IMoneyExpression operator -(IMoneyExpression minuend, IMoneyExpression subtrahend) | ||
public static IMoneyExpression<TUnderlyingType> operator -(IMoneyExpression<TUnderlyingType> minuend, IMoneyExpression<TUnderlyingType> subtrahend) | ||
=> minuend.Subtract(subtrahend); | ||
|
||
public static IMoneyExpression operator -(IMoneyExpression moneyExpression) | ||
=> moneyExpression.Multiply(-1); | ||
#endif | ||
public static IMoneyExpression<TUnderlyingType> operator -(IMoneyExpression<TUnderlyingType> moneyExpression) | ||
=> moneyExpression.Multiply(TUnderlyingType.NegativeOne); | ||
|
||
internal TState Accept<TState>(IMoneyExpressionVisitor<TState> visitor) | ||
internal TState Accept<TState>(IMoneyExpressionVisitor<TUnderlyingType, TState> visitor) | ||
where TState : notnull; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,20 @@ | ||
using System.Numerics; | ||
|
||
namespace Funcky; | ||
|
||
internal sealed class MoneyDistributionPart : IMoneyExpression | ||
internal sealed class MoneyDistributionPart<TUnderlyingType> : IMoneyExpression<TUnderlyingType> | ||
where TUnderlyingType : IFloatingPoint<TUnderlyingType> | ||
{ | ||
public MoneyDistributionPart(MoneyDistribution distribution, int index) | ||
public MoneyDistributionPart(MoneyDistribution<TUnderlyingType> distribution, int index) | ||
{ | ||
Distribution = distribution; | ||
Index = index; | ||
} | ||
|
||
public MoneyDistribution Distribution { get; } | ||
public MoneyDistribution<TUnderlyingType> Distribution { get; } | ||
|
||
public int Index { get; } | ||
|
||
TState IMoneyExpression.Accept<TState>(IMoneyExpressionVisitor<TState> visitor) | ||
TState IMoneyExpression<TUnderlyingType>.Accept<TState>(IMoneyExpressionVisitor<TUnderlyingType, TState> visitor) | ||
=> visitor.Visit(this); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,20 @@ | ||
using System.Numerics; | ||
|
||
namespace Funcky; | ||
|
||
internal sealed record MoneyProduct : IMoneyExpression | ||
internal sealed record MoneyProduct<TUnderlyingType> : IMoneyExpression<TUnderlyingType> | ||
where TUnderlyingType : IFloatingPoint<TUnderlyingType> | ||
{ | ||
public MoneyProduct(IMoneyExpression moneyExpression, decimal factor) | ||
public MoneyProduct(IMoneyExpression<TUnderlyingType> moneyExpression, TUnderlyingType factor) | ||
{ | ||
Expression = moneyExpression; | ||
Factor = factor; | ||
} | ||
|
||
public IMoneyExpression Expression { get; } | ||
public IMoneyExpression<TUnderlyingType> Expression { get; } | ||
|
||
public decimal Factor { get; } | ||
public TUnderlyingType Factor { get; } | ||
|
||
TState IMoneyExpression.Accept<TState>(IMoneyExpressionVisitor<TState> visitor) | ||
TState IMoneyExpression<TUnderlyingType>.Accept<TState>(IMoneyExpressionVisitor<TUnderlyingType, TState> visitor) | ||
=> visitor.Visit(this); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,20 @@ | ||
using System.Numerics; | ||
|
||
namespace Funcky; | ||
|
||
internal sealed record MoneySum : IMoneyExpression | ||
internal sealed record MoneySum<TUnderlyingType> : IMoneyExpression<TUnderlyingType> | ||
where TUnderlyingType : IFloatingPoint<TUnderlyingType> | ||
{ | ||
public MoneySum(IMoneyExpression leftMoneyExpression, IMoneyExpression rightMoneyExpression) | ||
public MoneySum(IMoneyExpression<TUnderlyingType> leftMoneyExpression, IMoneyExpression<TUnderlyingType> rightMoneyExpression) | ||
{ | ||
Left = leftMoneyExpression; | ||
Right = rightMoneyExpression; | ||
} | ||
|
||
public IMoneyExpression Left { get; } | ||
public IMoneyExpression<TUnderlyingType> Left { get; } | ||
|
||
public IMoneyExpression Right { get; } | ||
public IMoneyExpression<TUnderlyingType> Right { get; } | ||
|
||
TState IMoneyExpression.Accept<TState>(IMoneyExpressionVisitor<TState> visitor) | ||
TState IMoneyExpression<TUnderlyingType>.Accept<TState>(IMoneyExpressionVisitor<TUnderlyingType, TState> visitor) | ||
=> visitor.Visit(this); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,15 @@ | ||
using System.Numerics; | ||
using Funcky.Monads; | ||
|
||
namespace Funcky; | ||
|
||
public static class MoneyDistributionExtension | ||
{ | ||
public static IEnumerable<IMoneyExpression> Distribute(this IMoneyExpression moneyExpression, int numberOfSlices, Option<decimal> precision = default) | ||
public static IEnumerable<IMoneyExpression<TUnderlyingType>> Distribute<TUnderlyingType>(this IMoneyExpression<TUnderlyingType> moneyExpression, int numberOfSlices, Option<TUnderlyingType> precision = default) | ||
where TUnderlyingType : IFloatingPoint<TUnderlyingType> | ||
=> moneyExpression.Distribute(Enumerable.Repeat(element: 1, count: numberOfSlices), precision); | ||
|
||
public static IEnumerable<IMoneyExpression> Distribute(this IMoneyExpression moneyExpression, IEnumerable<int> factors, Option<decimal> precision = default) | ||
=> new MoneyDistribution(moneyExpression, factors, precision); | ||
public static IEnumerable<IMoneyExpression<TUnderlyingType>> Distribute<TUnderlyingType>(this IMoneyExpression<TUnderlyingType> moneyExpression, IEnumerable<int> factors, Option<TUnderlyingType> precision = default) | ||
where TUnderlyingType : IFloatingPoint<TUnderlyingType> | ||
=> new MoneyDistribution<TUnderlyingType>(moneyExpression, factors, precision); | ||
} |
Oops, something went wrong.