Swift在十进制格式中丢失精度



在使用Decimal类型处理货币输入时,我遇到了精度问题。问题出在格式化程序上。这是操场上最小的可复制代码:

let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.isLenient = true
formatter.maximumFractionDigits = 2
formatter.generatesDecimalNumbers = true
let text = "89806.9"
let decimal = formatter.number(from: text)?.decimalValue ?? .zero
let string = "(decimal)"
print(string)

它打印出89806.89999999999而不是89806.9。然而,大多数其他数字都很好(例如8980.9(。所以我不认为这是一个双精度与小数精度的问题。

编辑:

我需要使用格式化程序的原因是,有时我需要处理货币格式输入:

let text = "$89,806.9"
let decimal = formatter.number(from: text)?.decimalValue ?? .zero
print("(decimal)") // prints 89806.89999999999
let text2 = "$89,806.9"
let decimal2 = Decimal(string: text2)
print("(decimal2)") // prints nil

使用新的FormatStyle似乎可以生成正确的结果

let format = Decimal.FormatStyle
.number
.precision(.fractionLength(0...2))

let text = "89806.9"
let value = try! format.parseStrategy.parse(text)

下面是一个使用区域设置中的货币代码解析货币的示例

let currencyFormat = Decimal.FormatStyle.Currency
.currency(code: Locale.current.currencyCode!)
.precision(.fractionLength(0...2))
let amount = try! currencyFormat.parseStrategy.parse(text)

瑞典示例:

let text = "89806,9 kr"
print(amount)

89806.9

另一个选项是使用新的init for Decimal,它采用String和FormatStyle.Currency(或Number或Percent(

let amount = try Decimal(text, format: currencyFormat)

为了格式化这个值,我们可以在Decimal 上使用formatted(_:)

print(amount.formatted(currencyFormat))

输出(仍然是瑞典语(:

89 806,9 kr

我同意这是一个令人惊讶的错误,我会打开一个关于它的Apple反馈,但我也强烈建议切换到Decimal(string:locale:)而不是格式化程序,这将实现您的目标(也许isLenient部分除外(。

let x = Decimal(string: text)!
print("(x)") // 89806.9

如果你想修复小数位数,你可以通过Int使用* 100 / 100转换非常容易地应用舍入。(如果不清楚如何做到这一点,我会解释;它适用于Decimal,但不适用于Double。(

在Joakim Danielson回答之后,请参阅这篇关于格式风格的惊人文档

Decimal(10.01).formatted(.number.precision(.fractionLength(1))) // 10.0 Decimal(10.01).formatted(.number.precision(.fractionLength(2))) // 10.01 Decimal(10.01).formatted(.number.precision(.fractionLength(3))) // 10.010

惊人的详细文档

如果这严格来说是一个渲染问题,而您只是想将货币值从原始字符串转换为格式化字符串,那么就这样做吧。

let formatter = NumberFormatter()
formatter.numberStyle = .currency
let raw = "89806.9"
if let double = Double(raw),
let currency = formatter.string(from: NSNumber(value: double)) {
print(currency) // $89,806.90
}

如果涉及数学,那么在使用字符串格式化程序之前,我会向您指出为什么不使用Double或Float来表示货币?和如何使用Banker';将double四舍五入为int;s把C四舍五入作为一个很好的起点。

我得到了具有双值的响应,并删除了formatter.generatesDecimalNumbers行以获得工作。

let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.isLenient = true
formatter.maximumFractionDigits = 2
//formatter.generatesDecimalNumbers = true // I removed this line
let text = "$89806.9"
let double = formatter.number(from: text)?.doubleValue ?? .zero // converting as double or float
let string = "(double)"
print(string) // 89806.9
let anotherText = "$0.1"
let anotherDouble = formatter.number(from: anotherText)?.doubleValue ?? .zero // converting as double or float
let anotherString = "(anotherDouble)"
print(anotherString) // 0.1

最新更新