Skip to content

Instantly share code, notes, and snippets.

@oisdk
Last active March 10, 2023 20:40
Show Gist options
  • Save oisdk/569de8286f9f706749b7d0668836706a to your computer and use it in GitHub Desktop.
Save oisdk/569de8286f9f706749b7d0668836706a to your computer and use it in GitHub Desktop.
import Foundation
protocol Currency { static var sign: String { get } }
enum GBP: Currency { static let sign = "£" }
enum EUR: Currency { static let sign = "€" }
enum USD: Currency { static let sign = "$" }
protocol _Money {
associatedtype C: Currency
var amount: NSDecimalNumber { get }
}
struct Money<Cur: Currency>: _Money, CustomStringConvertible, FloatLiteralConvertible, IntegerLiteralConvertible {
typealias C = Cur
let amount: NSDecimalNumber
var description: String { return Cur.sign + String(amount) }
init(floatLiteral value: Double) { self.amount = NSDecimalNumber(double: value) }
init(integerLiteral value: Int) { self.amount = NSDecimalNumber(integer: value) }
init(_ amount: NSDecimalNumber) { self.amount = amount }
}
extension _Money where C == GBP {
var gbp: Money<GBP> { return Money(amount) }
var eur: Money<EUR> { return Money(amount.decimalNumberByMultiplyingBy(1.27)) }
var usd: Money<USD> { return Money(amount.decimalNumberByMultiplyingBy(1.44)) }
}
extension _Money where C == EUR {
var gbp: Money<GBP> { return Money(amount.decimalNumberByMultiplyingBy(0.79)) }
var eur: Money<EUR> { return Money(amount) }
var usd: Money<USD> { return Money(amount.decimalNumberByMultiplyingBy(1.13)) }
}
extension _Money where C == USD {
var gbp: Money<GBP> { return Money(amount.decimalNumberByMultiplyingBy(0.69)) }
var eur: Money<EUR> { return Money(amount.decimalNumberByMultiplyingBy(0.88)) }
var usd: Money<USD> { return Money(amount) }
}
let fivePound: Money<GBP> = 5 // £5
let threeEuro: Money<EUR> = 3 // €3
print(fivePound.eur) // €6.35
print(threeEuro.gbp) // £2.37
@davedelong
Copy link

This approach requires n² conversion methods, which is grossly inefficient. Also, you shouldn't be doing formatting manually in the description method. Here's a slightly different approach to address those two issues:

import Foundation

protocol Currency {
    static var code: String { get }
    static var factor: NSDecimalNumber { get }
}
enum GBP: Currency {
    static let code = "GBP"
    static let factor: NSDecimalNumber = 1.44
}
enum EUR: Currency {
    static let code = "EUR"
    static let factor: NSDecimalNumber = 1.13
}
enum USD: Currency {
    static let code = "USD"
    static let factor: NSDecimalNumber = 1.0
}

struct Money<Cur: Currency>: CustomStringConvertible, FloatLiteralConvertible, IntegerLiteralConvertible {
    let amount: NSDecimalNumber
    var description: String {
        let f = NSNumberFormatter()
        f.numberStyle = .CurrencyStyle
        f.currencyCode = Cur.code
        return f.stringFromNumber(self.amount)!
    }
    init(floatLiteral value: Double) { self.amount = NSDecimalNumber(double: value) }
    init(integerLiteral value: Int) { self.amount = NSDecimalNumber(integer: value) }
    init(_ amount: NSDecimalNumber) { self.amount = amount }

    func convertTo<C: Currency>() -> Money<C> {
        let baseAmount = amount.decimalNumberByMultiplyingBy(Cur.factor)
        let convertedAmount = baseAmount.decimalNumberByDividingBy(C.factor)
        return Money<C>(convertedAmount)
    }
}

extension Money {
    var gbp: Money<GBP> { return convertTo() }
    var eur: Money<EUR> { return convertTo() }
    var usd: Money<USD> { return convertTo() }
}

let fivePound: Money<GBP> = 5 // £5
let threeEuro: Money<EUR> = 3 // €3

print(fivePound.eur) // €6.35
print(threeEuro.gbp) // £2.37

@NatashaTheRobot
Copy link

Thanks @oisdk for the initial solution and @davedelong for optimizing it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment