使用JSONSerialization序列化货币时指定小数位数 [英] Specify number of decimals when serializing currencies with JSONSerialization
问题描述
NumberFormatter
使在屏幕上显示值时很容易格式化货币:
NumberFormatter
makes it quite easy to format currencies when presenting values on screen:
let decimal = Decimal(25.99)
let decimalNumberFormatter = NumberFormatter()
decimalNumberFormatter.numberStyle = .currencyAccounting
let output = decimalNumberFormatter.string(for: decimal)
// output = "$25.99"
上面的代码对于任何Decimal
或Double
值都适用.小数位数始终与所使用的语言环境相匹配.
The above code works well both for any Decimal
or Double
values. The amount of decimal digits always matches that of the locale being used.
证明将浮点货币值序列化为JSON并不是一件容易的事.
Turns our that serializing a floating point currency value to JSON is not that trivial.
具有以下序列化方法(请注意解开力):
Having the following serializing method (mind the force unwraps):
func serialize(prices: Any...) {
let data = try! JSONSerialization.data(withJSONObject: ["value": prices], options: [])
let string = String(data: data, encoding: .utf8)!
print(string)
}
然后我们可以用不同的值和类型来调用它.在某些情况下,Double
,Decimal
和NSDecimalNumber
(应从Swift的Decimal
桥接)无法正确呈现该值.
We can then call it with different values and types. Double
, Decimal
and NSDecimalNumber
(which should be bridged from Swift's Decimal
) fail to properly render the value in some cases.
serialize(prices: 125.99, 16.42, 88.56, 88.57, 0.1 + 0.2)
// {"value":[125.99,16.42,88.56,88.56999999999999,0.3]}
serialize(prices: Decimal(125.99), Decimal(16.42), Decimal(88.56), Decimal(88.57), Decimal(0.1) + Decimal(0.2))
// {"value":[125.98999999999997952,16.420000000000004096,88.56,88.57,0.3]}
serialize(prices: NSDecimalNumber(value: 125.99), NSDecimalNumber(value: 16.42), NSDecimalNumber(value: 88.56), NSDecimalNumber(value: 88.57), NSDecimalNumber(value: 0.1).adding(NSDecimalNumber(value: 0.2)))
// {"value":[125.98999999999997952,16.420000000000004096,88.56,88.57,0.3]}
我不希望将数字序列化为货币(不需要货币符号,整数(5
)或单个小数位(0.3
)都可以).但是我正在寻找一种解决方案,其中序列化的输出中所包含的位数不超过给定货币允许的小数位数(区域设置).
I'm not looking to serialize numbers as currencies (no need for currency symbol, integers (5
) or single decimal position (0.3
) are fine). However I'm looking for a solution where the serialized output contains no more than the number of decimals allowed by a given currency (locale).
这是什么,当将浮点值序列化为JSON时,有没有办法限制或指定要使用的小数位数?
This is, is there any way to limit or specify the number of decimals to be used when serializing floating point values to JSON?
更新#1:
经过更多数据类型的测试,令人惊讶的是,似乎Float
和Float32
都适用于两位十进制的货币. Float64
失败,显示为Double
(可能是相同类型的别名).
Update #1:
Tested with more data types, surprisingly seems like both Float
and Float32
work well for two-decimal currencies. Float64
fails as Double
(probably they are an alias of the same type).
serialize(prices: Float(125.99), Float(16.42), Float(88.56), Float(88.57), Float(0.1) + Float(0.2))
// {"value":[125.99,16.42,88.56,88.57,0.3]}
serialize(prices: Float32(125.99), Float32(16.42), Float32(88.56), Float32(88.57), Float32(0.1) + Float32(0.2))
// {"value":[125.99,16.42,88.56,88.57,0.3]}
serialize(prices: Float64(125.99), Float64(16.42), Float64(88.56), Float64(88.57), Float64(0.1) + Float64(0.2))
// {"value":[125.99,16.42,88.56,88.56999999999999,0.3]}
不过,很难知道它们是否在所有情况下都能正常工作. Float80
失败,并出现_NSJSONWriter
异常.
Hard to know if they work well in all cases, though. Float80
fails to serialize with a _NSJSONWriter
exception.
推荐答案
在对此事进行了一些研究之后,一位同事发现,使用NSDecimalNumberHandler
舍入指定行为的值可以解决JSON序列化问题.
After doing some research in this matter, a coworker found that rounding the values specifying a behavior using NSDecimalNumberHandler
solves the JSON serialization issue.
fileprivate let currencyBehavior = NSDecimalNumberHandler(roundingMode: .bankers, scale: 2, raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: true)
extension Decimal {
var roundedCurrency: Decimal {
return (self as NSDecimalNumber).rounding(accordingToBehavior: currencyBehavior) as Decimal
}
}
按照文章中的示例代码,我们将获得所需的输出:
Following the example code from the post, we get the desired output:
serialize(prices: Decimal(125.99).roundedCurrency, Decimal(16.42).roundedCurrency, Decimal(88.56).roundedCurrency, Decimal(88.57).roundedCurrency, (Decimal(0.1) + Decimal(0.2)).roundedCurrency)
// {"value":[125.99,16.42,88.56,88.57,0.3]}
有效!对10000个值(从0.0到99.99)进行测试,没有发现问题.
It works! Ran a test for 10000 values (from 0.0 to 99.99) and found no issues.
如果需要,可以将小数位数调整为当前语言环境的小数位数:
If needed, the scale can be adjusted to the number of decimals from the current locale:
var currencyFormatter = NumberFormatter()
currencyFormatter.numberStyle = .currencyAccounting
let scale = currencyFormatter.maximumFractionDigits
// scale == 2
这篇关于使用JSONSerialization序列化货币时指定小数位数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!