Skip to content

Commit

Permalink
Simplify the CurrencyValue protocol (#46)
Browse files Browse the repository at this point in the history
Motivations:

The current `CurrencyValue` protocol has a few requirements with default
implementations that are not meant to be extension points on the
protocol.

In addition, a few aspects of how the protocol can be used are a bit
rough in either performance or generic ergonomics.

Modifications:

- Add: `AdditiveArithmetic` conformance for free
- Add: `@inlinable` annotations to remaining mutating and localization
methods
- Change: `minorUnits` and `roundedAmount` properties / initializers to
be pure extensions

Result:

Developers are able to use `CurrencyValue` in greater generic contexts
with slightly better performance capabilities, while also having more
protection from incorrectly override expected behavior.
  • Loading branch information
Mordil authored Jan 7, 2025
1 parent 9fb8e01 commit 42e8c8a
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 36 deletions.
5 changes: 5 additions & 0 deletions Sources/Currency/CurrencyValue+Arithmetic.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ extension CurrencyValue {

/// Subtracts the given other amount from the current exactAmount.
/// - Parameter amount: The other amount to subtract.
@inlinable
public mutating func subtract(_ amount: Self) {
self -= amount
}
Expand All @@ -128,6 +129,7 @@ extension CurrencyValue {

/// Subtracts the given other amount from the current exactAmount.
/// - Parameter amount: The other amount to subtract.
@inlinable
public mutating func subtract(_ amount: some BinaryInteger) {
self -= amount
}
Expand All @@ -144,6 +146,7 @@ extension CurrencyValue {

/// Subtracts the given other amount from the current exactAmount.
/// - Parameter amount: The other amount to subtract.
@inlinable
public mutating func subtract(_ amount: Decimal) {
self -= amount
}
Expand All @@ -170,6 +173,7 @@ extension CurrencyValue {

/// Multiplies the current exactAmount by the given other amount.
/// - Parameter amount: The other amount to multiply by.
@inlinable
public mutating func multiply(by amount: some BinaryInteger) {
self *= amount
}
Expand All @@ -186,6 +190,7 @@ extension CurrencyValue {

/// Multiplies the current exactAmount by the given other amount.
/// - Parameter amount: The other amount to multiply by.
@inlinable
public mutating func multiply(by amount: Decimal) {
self *= amount
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/Currency/CurrencyValue+StringRepresentation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ extension CurrencyValue {
/// - Parameters:
/// - locale: The Locale to localize the value for. The default is `.current`, ig. the runtime environment's Locale.
/// - Returns: A localized String representation of the currency value.
@inlinable
public func localizedString(for locale: Locale = .current) -> String {
return "\(localize: self, for: locale)"
}
Expand Down Expand Up @@ -165,6 +166,7 @@ extension CurrencyValue {
/// - Parameters:
/// - formatter: The pre-configured formatter to use.
/// - Returns: A localized String representation of the currency value.
@inlinable
public func localizedString(using formatter: NumberFormatter) -> String {
return "\(localize: self, with: formatter)"
}
Expand Down
69 changes: 33 additions & 36 deletions Sources/Currency/CurrencyValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,12 @@ public protocol CurrencyValue:
CustomStringConvertible, CustomDebugStringConvertible, CustomPlaygroundDisplayConvertible,
CustomReflectable,
Comparable, Hashable,
ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral
ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral,
AdditiveArithmetic
{
/// The information describing the currency.
static var descriptor: CurrencyDescriptor.Type { get }

/// The amount represented as a whole number of the curreny's "minor units".
///
/// For example, as the USD uses 1/100 for its minor unit,
/// with the value of `USD 1.01`, ``minorUnits`` will be the value `101`.
/// - Important: Fractional values (of the currency) are impossible to be represented in this value.
var minorUnits: CurrencyMinorUnitRepresentation { get }

/// The exact amount of money being represented,
/// even if the value is fractional of what the currency uses for its minor units.
///
Expand All @@ -50,51 +44,58 @@ public protocol CurrencyValue:
/// For an amount that is likely to be used in "every day" usage, see ``roundedAmount``
var exactAmount: Decimal { get }

/// The cannonical representation of the ``exactAmount`` of money this value represents,
/// rounded using the `bankers` method to the currency's ``CurrencyDescriptor/minorUnits``.
///
/// For example:
///
/// let usd = USD(10.007)
/// let yen = JPY(100.9)
/// let dinar = KWD(100.0019)
/// print(usd, yen, dinar)
/// // "USD(10.01), JPY(101), KWD(100.002)
var roundedAmount: Decimal { get }

/// Creates a representation of the currency with the exact value as given.
/// - Parameter exactAmount: The amount this instance should represent, without any rounding.
init(exactAmount: Decimal)
}

/// Creates a representation of the currency, converting the minor units representation to a decimal format.
///
/// e.g. If you provide the value `100` for a currency with `2` minor units, the `exactAmount` will be `1.00`.
/// - Parameter minorUnits: The minor units this value will represent.
init(minorUnits: CurrencyMinorUnitRepresentation)
// MARK: Defaults

/// Rounds the ``exactAmount`` of money being represented using the given rounding method
/// to the currency's ``CurrencyDescriptor/minorUnits``.
/// - Parameter roundingMode: The desired rounding mode to use on the original ``exactAmount``.
/// - Complexity: O(1)
/// - Returns: The rounded amount.
func roundedAmount(using roundingMode: NSDecimalNumber.RoundingMode) -> Decimal
extension CurrencyValue where Self: CurrencyDescriptor {
public static var descriptor: CurrencyDescriptor.Type { Self.self }
}

// MARK: Defaults
// MARK: Extensions

extension CurrencyValue {
/// The amount represented as a whole number of the curreny's "minor units".
///
/// For example, as the USD uses 1/100 for its minor unit,
/// with the value of `USD 1.01`, ``minorUnits`` will be the value `101`.
/// - Important: Fractional values of the currency are truncated.
public var minorUnits: CurrencyMinorUnitRepresentation {
let scaledAmount = self.exactAmount * Self.descriptor.minorUnitsCoefficient(for: .exactAmount)
return .init(scaledAmount.int64Value)
}

/// The "every day" representation of the ``exactAmount`` of money this value represents,
/// rounded using the `bankers` method to the currency's ``CurrencyDescriptor/minorUnits``.
///
/// For example:
/// ```swift
/// let usd = USD(10.007)
/// let yen = JPY(100.9)
/// let dinar = KWD(100.0019)
///
/// print(usd, yen, dinar)
/// // "USD(10.01), JPY(101), KWD(100.002)
/// ```
public var roundedAmount: Decimal { self.roundedAmount(using: .bankers) }

/// Creates a representation of the currency, converting the minor units representation to a decimal format.
///
/// e.g. If you provide the value `100` for a currency with `2` minor units, the `exactAmount` will be `1.00`.
/// - Parameter minorUnits: The minor units this value will represent.
public init(minorUnits: CurrencyMinorUnitRepresentation) {
let amount = Decimal(minorUnits) * Self.descriptor.minorUnitsCoefficient(for: .minorUnits)
self.init(exactAmount: amount)
}

/// Rounds the ``exactAmount`` of money being represented using the given rounding method
/// to the currency's ``CurrencyDescriptor/minorUnits``.
/// - Parameter roundingMode: The desired rounding mode to use on the original ``exactAmount``.
/// - Complexity: O(1)
/// - Returns: The rounded amount.
public func roundedAmount(using roundingMode: NSDecimalNumber.RoundingMode) -> Decimal {
var sourceAmount = self.exactAmount
var result = Decimal.zero
Expand All @@ -103,10 +104,6 @@ extension CurrencyValue {
}
}

extension CurrencyValue where Self: CurrencyDescriptor {
public static var descriptor: CurrencyDescriptor.Type { Self.self }
}

// MARK: Equatable

extension CurrencyValue {
Expand Down

0 comments on commit 42e8c8a

Please sign in to comment.