我有一个iOS应用程序,将对代表美元货币的数字执行许多基本算术运算(例如25.00表示$25.00)。
我在其他语言(如Java和Javascript)中使用Double数据类型时遇到很多问题,因此我想知道在Swift中使用哪种最佳数据类型来表示货币。
使用Decimal
,并 确保正确初始化!
// 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
}
在货币金额中进行双精度(Double)或单精度(Float)算术运算会产生不准确的结果。 这是因为Double
和Float
类型不能准确地表示大多数十进制数。更多信息请点击此处。
简而言之:使用Decimal
或Int
对货币金额执行算术运算
Decimal
转换回 String
的正确方式?我不确定 StackOverflow 上的其他答案是否展示得正确。 - zeeshanNSNumber(floatLiteral: 32.111).decimalValue
也是不正确的。问题在于将十进制数(浮点字面量)转换为有损二进制数(double
),然后再转换为十进制数(Decimal
)。如果您想要精度,请始终从字符串初始化。 - SulthanDecimal(string:locale:)
不识别分组分隔符。对于 "32111.01",结果将是正确的。 - Roman Aliyev我建议你从为Decimal
创建一个typealias
开始。示例:
typealias Dollars = Decimal
let a = Dollars(123456)
let b = Dollars(1000)
let c = a / b
print(c)
输出:
123.456
如果您尝试从字符串中解析货币值,请使用NumberFormatter
并将其generatesDecimalNumbers
属性设置为true
。Double
初始化Decimal
很容易产生舍入误差。例如: Decimal(2.13) == 2.129999999999999488
。 - EricDouble
转换为Decimal
。我的回答展示了如何通过除以10的幂来准确地创建带有小数位的Decimal
。 - rob mayoff有一个非常好用的库叫做Money:
let money: Money = 100
let moreMoney = money + 50 //150
除此之外,还有许多不错的功能,比如类型安全的货币:
let euros: EUR = 100
let dollars: USD = 1500
euros + dollars //Error
Currency
是一个巨大的枚举,在预先生成(不要忘记货币价值只是一个没有其货币的数字 - 它们都属于一起):struct Money: Hashable, Equatable {
let value: Decimal
let currency: Currency
}
extension Money {
var string: String {
CurrencyFormatter.shared.string(from: self)
}
}
Decimal
和它的初始化器:Decimal(string: "12345.67890", locale: Locale(identifier: "en-US"))
Float
和Double
字面量会影响十进制结果的精度。同样适用于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 ✅
Int
(将美元金额乘以100来存储,然后在访问时将其反转并转换为Double
)。然而,在实现了一个多货币系统之后,其中各种货币具有不同数量的小数位数,跟踪存储的Int
或访问的Double
金额以及它是原始货币还是基础货币,使用Int
方法开销太大。当使用Double
时,数学计算更容易,但最终我也推荐使用NSDecimalNumber
。 - blwinters