在Swift中使用NumberFormatter拼出货币金额的小数部分

问题描述

我需要将货币金额转换为以纯文本(即支票,合同和其他法律文件)表示价值的字符串。 这可以通过使用数字样式NumberFormatter时将.spellOut转换为纯文本形式的数字来完成:

var curAmount = 10097.43
var fmtAmount : String

let formatter = NumberFormatter()
formatter.locale = Locale.init(identifier:"en_US.UTF-8")  // for demo only 
//formatter.locale = Locale.current  // Normal case is user's preferred locale
formatter.numberStyle = .spellOut
if let fmtAmount = formatter.string(from: curAmount as NSNumber) {
    print ("Amount: \(fmtAmount)")
}

对于数字的整数部分,它可以很好地工作,但是不幸的是,在我尝试过的所有六种语言中,小数部分都被当作一个独立的数字序列来处理。例如:

ten thousand ninety-seven point four three

但是我需要小数部分也要用数字表示,例如

ten thousand ninety-seven point forty three
ten thousand ninety-seven dollars forty three

我当然可以使用字符串,删除"point"之后的所有内容,将小数部分乘以100,生成第二个数字字符串并将两个字符串连接在一起。但这不适用于使用外语的语言环境,也不适用于在该点之后具有零或三位数的货币。

是否有一种方法可以使小数部分作为纯文本整数正确处理,即使用格式化程序的一些细微参数,还是开箱即用?并有一种方法可以根据区域设置的语言在正确的位置还包括货币名称(例如,用数字样式.currencyPlural拼写出来)?

解决方法

可能是这样的一种方式:

O(2^n)
,

初步说明:这里的主要问题是,我想要一个国际化的solutino,它依赖于内置的iOS / macOS功能。因此,我不能使用基于给定十进制分隔符的任何解决方案,因为它会根据用户的语言环境而改变,也不能使用硬编码的字符串。这就是为什么我只能赞扬Roman的解决方案非常优雅的代码,而不能接受,因为它不能完全满足要求。

let value = 12345.678      // value to spell
currency: String? = nil   // ISO currency if you want one specific

1。初始化格式化程序:

let formatter = NumberFormatter()  // formatter with user's default locale

如果我想使用其他语言或其他国家/地区格式,则必须明确覆盖区域设置:

formatter.locale = Locale.init(identifier: "en_US.UTF-8")  // for demo only 

如果我使用语言环境国家/地区的货币,则效果很好。但是,如果我使用其他货币,则格式化程序的行为对于某些格式化样式可能是错误的。因此,诀窍是使用特定于货币的语言环境

if currency != nil {
    let newLocale = "\(formatter.locale.identifier)@currency=\(currency!)"
    formatter.locale = Locale(identifier:newLocale)
}

有趣的Foundation功能是,当我更改货币时,特定于货币的语言环境设置(即货币代码,货币符号和位数)会自动更改:

formatter.numberStyle = .currency
let decimals = max(formatter.minimumFractionDigits,formatter.maximumFractionDigits)

2。以正确的语法形式获取完整的货币名称

现在我有了decimals,但是我想要该货币的全名spelledCurrency。问题是某些语言具有中性。对于某些语言,您必须将其单数写成1个单位,但从2开始写复数。对于某些语言,小数点在单数和复数之间的选择中很重要。幸运的是,NumberFormatter允许使用样式.currencyPlural解决此问题。这种怪异的样式用数字写数字,但是根据特定语言的规则写普通货币。这很神奇:

问题是我只需要货币名称:没有空格,没有分隔符,没有数字。我也希望它适用于日语(语言环境"ja_JP.UTF-8")。幸运的是,我们可以很容易地过滤字符串:

formatter.numberStyle = .currencyPlural
var spelledCurrency : String
if let plural = formatter.string(from: value as NSNumber) {
    // Keep only the currency spelling: trim spaces,punctuation and digits
    // ASSUMPTION: decimal and thousand separators belong to punctuation
    var filter = CharacterSet()
    filter.formUnion(.punctuationCharacters)
    filter.formUnion(.decimalDigits)
    filter.formUnion(.whitespaces)
    spelledCurrency = plural.trimmingCharacters(in: filter)
 }
 else {
    // Fall back - worst case: the ISO code XXX for no currency is used
    spelledCurrency = formatter.currencyCode ?? (formatter.internationalCurrencySymbol ?? "XXX")
 }

3。分隔数字分量

我使用了定义为value的{​​{1}}。但是您可以轻松地适应Double类型,这对于货币来说更强大。诀窍是使Decimal以货币单位为单位,而wholePart(即用整数表示的部分,该整数与货币的子单位数量相对应(由于货币的小数位数):

wholeDecimal

4。拼出组件

在这里,我们使用本机let decimalPart = value.truncatingRemainder(dividingBy: 1) let wholePart = value - decimalPart let wholeDecimals = floor(decimalPart * pow(10.0,Double(decimals))) 功能来拼写数字。如果您尝试学习外语,那就太棒了;-)

.spellOut

5。最终组装

我们现在必须组装组件。但是,与其说二十美元指向五点七点,不如说是二十美元五十七美分。我已经决定将formatter.numberStyle = .spellOut let wholeString = formatter.string(from: wholePart as NSNumber) ?? "???" let decimalString = formatter.string(from: wholeDecimals as NSNumber) ?? "???" 替换为货币的本地化名称,再替换为子单位数,而不表示子单位本身。它可以使用我所知道的几种语言工作:

"and"

只需打印(或返回)最终值:

// ASSUMPTION: currency is always between whole and fractional
// TO BE VERIFIED: Ok for Right to left writing?
var spelled : String
if (decimals>0 && wholeDecimals != 0) {
    spelled = "\(wholeString) \(spelledCurrency) \(decimalString)"
} else {
    spelled = "\(wholeString) \(spelledCurrency)"
}

6。测试

以下是一些语言的测试结果:

print (">>>>> \(spelled)")

最后一个测试显示了en,USD: twelve thousand three hundred forty-five US dollars sixty-seven fr,EUR: douze mille trois cent quarante-cinq euros soixante-sept de,EUR: zwölf­tausend­drei­hundert­fünf­und­vierzig Euro sieben­und­sechzig nl,EUR: twaalf­duizend­drie­honderd­vijf­en­veertig euro zeven­en­zestig it,EUR: dodici­mila­tre­cento­quaranta­cinque euro sessanta­sette es,EUR: doce mil trescientos cuarenta y cinco euros sesenta y siete ja,JPY: 一万二千三百四十五 円 fr,TND: douze mille trois cent quarante-cinq dinars tunisiens six cent soixante-dix-sept 的舍入误差,因此最好使用Double

我还使用了1到2欧元之间的金额,并且带有witout子单元和整数。

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...