namespace Prices; /// Turns a list price into what the customer actually pays. public static class PriceCalculator { /// Applies a percentage discount to a price, e.g. 25 for a quarter off. public static decimal ApplyDiscount(decimal price, decimal discountPercent) { if (price < 0) { throw new ArgumentOutOfRangeException(nameof(price), "A price cannot be negative."); } if (discountPercent is < 0 or > 100) { throw new ArgumentOutOfRangeException(nameof(discountPercent), "A discount is between 0 and 100 percent."); } return price * (1m - discountPercent / 100m); } /// Rounds an amount to the nearest 0.05, rounding halves away from zero. public static decimal RoundToFiveRappen(decimal amount) { return Math.Round(amount * 20m, MidpointRounding.AwayFromZero) / 20m; } /// Returns the quantity discount percent for a quantity: 0, 5 or 10. public static int GetQuantityDiscountPercent(int quantity) { if (quantity < 1) { throw new ArgumentOutOfRangeException(nameof(quantity), "A quantity must be at least 1."); } return quantity switch { >= 50 => 10, >= 10 => 5, _ => 0, }; } /// /// Calculates the total: percentage discount on the unit price first, then the quantity /// discount on the total, then rounds the end amount to the nearest 0.05. /// public static decimal CalculateTotal(decimal unitPrice, int quantity, decimal discountPercent) { if (unitPrice < 0) { throw new ArgumentOutOfRangeException(nameof(unitPrice), "A price cannot be negative."); } if (quantity < 1) { throw new ArgumentOutOfRangeException(nameof(quantity), "A quantity must be at least 1."); } if (discountPercent is < 0 or > 100) { throw new ArgumentOutOfRangeException(nameof(discountPercent), "A discount is between 0 and 100 percent."); } var discountedUnitPrice = ApplyDiscount(unitPrice, discountPercent); var totalBeforeQuantityDiscount = discountedUnitPrice * quantity; var quantityDiscountPercent = GetQuantityDiscountPercent(quantity); var totalBeforeRounding = totalBeforeQuantityDiscount * (1m - quantityDiscountPercent / 100m); return RoundToFiveRappen(totalBeforeRounding); } }