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

如何解决在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子单元和整数。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 <select id="xxx"> SELECT di.id, di.name, di.work_type, di.updated... <where> <if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 <property name="dynamic.classpath" value="tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams['font.sans-serif'] = ['SimHei'] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -> systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping("/hires") public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate<String
使用vite构建项目报错 C:\Users\ychen\work>npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-