在 Swift 中使用 NumberFormatter 格式化大十进制数

Large decimal number formatting using NumberFormatter in Swift

我这样做是为了格式化数字,但是对于大数字它会失败

let formatter = NumberFormatter()
formatter.numberStyle = .decimal


    if let number = formatter.number(from: "123456789123456789123") , let str = formatter.string(from:number){

        print(number)
        print(str)
    }

它打印

123456789123456800000
123,456,789,123,456,800,000

它应该打印

123456789123456789123
123,456,789,123,456,789,123

我觉得应该是数字溢出了,请问有什么办法可以实现这种事情吗

您可以创建一个 Decimal 明确地解决提到的错误

let formatter = NumberFormatter()
formatter.numberStyle = .decimal
if let decimalNumber = Decimal(string: "123456789123456789123"), let str = formatter.string(from:decimalNumber as NSNumber) {
    print(decimalNumber)
    print(str)
}

这个问题的根源是three-fold:

  • 从版本 5.6 开始,Swift 不支持十进制文字。
  • Decimal() 最多支持 38 位精度。
  • Double() 支持大约 17 位精度。

因此,当使用 Decimals 表示精度超过 17 位的数字时,千万不要做任何将 Decimal 的值转换为 Double 的事情。这包括用文字初始化:

print (Decimal(1234567890.12345678901234567890)) 
// 1234567890.1234569216
print (Decimal(string: "1234567890.12345678901234567890")!) 
// 1234567890.12345678901234567890

Decimal(1234567890.12345678901234567890) 产生值 1234567890.1234569216,因为 1234567890.12345678901234567890 是一个 Double 文字,精度限制为大约 17 位数字。

遗憾的是,NumberFormatter 显然从未收到有关此问题的备忘录。

var dec = Decimal(string: "1234567890.12345678901234567890")!
let f = NumberFormatter()
f.numberStyle = .decimal
f.minimumFractionDigits = 20
print (f.string(from: dec as NSDecimalNumber)!) // 1,234,567,890.12346000000000000000

因此,目前无法Swift 准确生成精度超过 17 位的本地化十进制数字。例如,NumberFormatter 无法生成字符串 1,234,567,890.12345678901234567890,即使 Decimal 可以表示该精确值。

您可以使用 String(describing:)non-localized 获得 Decimals 的字符串表示

print (String(describing:(Decimal(string: "1234567890.12345678901234567890")!)))
// 1234567890.1234567890123456789

对于我自己的一个应用程序,我制作了一个“穷人的”小数格式化程序,它使用所需区域设置中的小数点分隔符简单地输出完整值:

let locale = Locale(identifier: "fr_FR")
dec = Decimal(string: "1234567890,12345678901234567890", locale: locale)!

let formatter = NumberFormatter()
formatter.locale = locale
let localizedSeparator = String(formatter.decimalSeparator.first ?? ".")

let fractionDigits = 20
var strings = String(describing:(dec)).split(separator: (Locale.current.decimalSeparator?.first) ?? "." )
var result = strings[0]
if fractionDigits > 0 {
    if strings.count > 1 {
        while strings[1].count < fractionDigits { strings[1] += "0" }
        result += localizedSeparator + String(strings[1].prefix(fractionDigits))
    }
    else {
        result += localizedSeparator + (0..<fractionDigits).map{_ in "0"}
    }
}

print (result) // 1234567890,12345678901234567890