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 !
// 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 😀
}
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.