简体   繁体   中英

Which Swift datatype do I use for currency

I have an iOS application that will be performing a lot of basic arithmetic on numbers representing USD currency (eg 25.00 representing $25.00).

I have gotten into a lot of trouble using the datatype Double in other languages like Java and Javascript so I would like to know the best datatype to use for currency in Swift.

Use Decimal , and make sure you initialize it properly !

CORRECT


// Initialising a Decimal from a Double:
let monetaryAmountAsDouble = 32.111
let decimal: Decimal = NSNumber(floatLiteral: 32.111).decimalValue
print(decimal) // 32.111  😀
let result = decimal / 2
print(result) // 16.0555 😀


// Initialising a Decimal from a String:
let monetaryAmountAsString = "32,111.01"

let formatter = NumberFormatter()
formatter.locale = Locale(identifier: "en_US")
formatter.numberStyle = .decimal

if let number = formatter.number(from: monetaryAmountAsString) {
    let decimal = number.decimalValue
    print(decimal) // 32111.01 😀
    let result = decimal / 2.1
    print(result) // 15290.9571428571428571428571428571428571 😀
}

INCORRECT

let monetaryAmountAsDouble = 32.111
let decimal = Decimal(monetaryAmountAsDouble) 
print(decimal) // 32.11099999999999488  😟

let monetaryAmountAsString = "32,111.01"
if let decimal = Decimal(string: monetaryAmountAsString, locale: Locale(identifier: "en_US")) {
    print(decimal) // 32  😟
}

Performing arithmetic operations on Double s or Float s representing currency amounts will produce inaccurate results. This is because the Double and Float types cannot accurately represent most decimal numbers. More information here .

Bottom line: Perform arithmetic operations on currency amounts using Decimal s or Int

I suggest you start with a typealias for Decimal . Example:

typealias Dollars = Decimal
let a = Dollars(123456)
let b = Dollars(1000)
let c = a / b
print(c)

Output:

123.456

There's a really nice lib called Money :

let money: Money = 100
let moreMoney = money + 50 //150

There a lot of nice features besides that, such as type-safe currencies:

let euros: EUR = 100
let dollars: USD = 1500
euros + dollars //Error

Binary operator '+' cannot be applied to operands of type 'EUR' (aka '_Money') and 'USD' (aka '_Money')

The Flight-School/Money is probably the best choice for a project full of monies.

In theending of README @mattt provides a nice solution for a simple Money type:

struct Money {
    enum Currency: String {
      case USD, EUR, GBP, CNY // supported currencies here
    }

    var amount: Decimal
    var currency: Currency
}

Chapter 3 in 'Flight School Guide to Swift Numbers' is provides excellent intro into the topic.

We use this approach with Currency being a huge enum that has been generated in advance (don't forget a monetary value is just a number without its currency - they both belong together):

struct Money: Hashable, Equatable {

    let value: Decimal
    let currency: Currency
}

extension Money {

    var string: String {
        CurrencyFormatter.shared.string(from: self)
    }
}

You need the Decimal and its initializer:

Decimal(string: "12345.67890", locale: Locale(identifier: "en-US"))

When considering the other answers, please note that Float and Double literals affect the accuracy of the Decimal result. The same applies to the NumberFormatter .

Decimal(5.01) // 5.009999999999998976 ❌

Decimal(floatLiteral: 5.01) // 5.009999999999998976 ❌

NSNumber(floatLiteral: 0.9999999).decimalValue // 0.9999999000000001 ❌

let decimalFormatter = NumberFormatter()
decimalFormatter.numberStyle = .decimal
decimalFormatter.number(from: "0.9999999")?.decimalValue // 0.9999999000000001 ❌

Decimal(string: "5.01", locale: Locale(identifier: "en-US")) // 5.01 ✅
Decimal(string: "0.9999999", locale: Locale(identifier: "en-US")) // 0.9999999 ✅

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM