JavaScript中的货币计算 2021-11-08 默认分类 暂无评论 2256 次阅读 ![1636297514.png](http://www.guobacai.com/usr/uploads/2021/11/2411108166.png) > 当你用钱做计算时,每一分钱都需要被计算出来。不幸的是,JS的Number类型并不能胜任这项工作。在这篇文章中,Julio Sampaio告诉我们原因,并教我们如何在JavaScript中以正确的方式进行货币计算。 **现代编程语言最令人好奇的一点是**,当有人决定创建一个新的编程语言时,会对所接受的数据类型和内部辅助库进行大量的思考。 想一想你以前使用过的所有编程语言。他们有多少种处理日期和时间的方法?他们中的大多数可能会发布至少一种处理这种数据类型的方法,因为它是开发人员编程生活中非常存在的一种类型。 那么,钱是怎么回事呢?银行、经纪人、网上购物等都需要以编程方式处理金钱。而且很长时间以来都是这样。 由于缺乏代表性,货币类型的处理方式多种多样,这取决于你所使用的语言。因此,一些隐患就出现了。 在这篇文章中,我们将更详细地探讨这些常见的陷阱,以及在JavaScript中处理货币的最佳选择。 ## **共识的隐患** 在我们深入探讨这些陷阱之前,首先让我们了解一下进行货币计算需要什么。 自2002年马丁-福勒(Martin Fowler)发布他那本名为《企业应用架构模式》(Patterns of Enterprise Application Architecture)的广受好评的书以来,我们有了一个处理货币价值的伟大模型。这一切都归结为两个属性,金额和货币,以及几个预期的操作,包括_+, -, *, /, >, >=, <, <=, 和 =。 想一想它们吧。如果我们不再把货币看作一个简单的数字,而是开始把它看作一个由两个基本属性和一些处理比较、转换和计算的方法组成的数据结构,那么我们就解决了涉及这种数据类型的大部分问题。 换句话说,要进行货币计算,你总是需要一个金额和一种货币,以及对它们进行操作的方法(即通过方法/函数)。 从JavaScript的角度来看,一个Money对象可以,比如说,持有这两个道具,并暴露一些计算的函数,就可以完成这个工作。 ## 不要使用浮动点 在处理钱的时候,你也需要存储美分。对于许多开发者来说,将这类数值存储为小数是正确的决定,因为有小数位。 通常,它们被表示为10的幂的一个单位。 10² = 1美元中的100美分 10³ = 10美元中的1000美分 ... 然而,在计算机中用浮点数表示货币会带来一些问题,我们在这里已经看到了。 浮点数在你的电脑上通过不同的算术方式存在。由于你的计算机利用二进制系统来存储十进制数字,你最终会产生不一致的计算结果。 ``` **0.2233 + 0.1 **// 结果为0.32330000000000003 ``` 发生这种情况是因为计算机试图尽可能地四舍五入以获得最佳结果。它也会砍掉那些太大的数字,比如说定期的什一税。 你可以自己决定通过例如Math.ceil来对前面的操作结果进行四舍五入。 ``` **Math.ceil(0.2233 + 0.1) **//结果为1 ``` 然而,这种方法仍然有问题,因为在这个过程中你会损失几分钱。根据你正在开发的应用程序的类型,这样的损失可能代表了客户或你的企业损失了很多钱。 由于这些问题,把钱表现为一个浮动对象并不是一个推荐的方法。如果你仍然有兴趣了解这个问题的具体细节,我强烈建议你阅读Oracle的文章。关于浮点运算,每个计算机科学家都应该知道什么。 ## 也不要使用Number 像许多其他语言一样,Number是一个原始的包装对象,当开发者需要表示或处理数字时,从整数到小数,都会使用它。 此外,由于它是双精度64位二进制格式的IEEE 754值,它也带来了我们刚刚在上一节谈到的相同威胁。 此外,Number也缺乏Fowler创造完美货币结构的条件之一:货币。如果你的应用程序目前只与一种货币打交道,那是完全可以的。然而,如果未来情况发生变化,它可能会很危险。 国际化API ECMAScript国际化API是一项集体努力,为国际目的提供标准化的格式化。它允许应用程序决定他们需要哪些功能以及如何处理这些功能。 在所提供的众多功能中,我们有数字格式化,它还包括基于指定地区的货币价值格式化。 请看下面的例子。 ``` var formatterUSD = new Intl.NumberFormat('en-US'); var formatterBRL = new Intl.NumberFormat('pt-BR'); var formatterJPY = new Intl.NumberFormat('ja-JP'); console.log(formatterUSD.format(0.2233 + 0.1)); // logs "0.323" console.log(formatterBRL.format(0.2233 + 0.1)); // logs "0,323" console.log(formatterJPY.format(0.2233 + 0.1)); // logs "0.323" ``` 我们正在创建三个不同的格式化器,分别为美国、巴西和日本货币传递不同的地域性。这很好地说明了这个API在同时接受金额和货币并对其进行灵活计算方面是多么强大。 请注意小数点系统是如何从一个国家变为另一个国家的,以及Intlab API是如何正确计算出我们所有不同货币的货币总和结果的。 如果你想设置最大有效位数,只需将代码改为。 ``` var formatterUSD = new Intl.NumberFormat('en-US', { 最大有效数字:2 }); console.log(formatterUSD.format(0.2233 + 0.1)); // 记录 "0.32" ``` 这就是你在加油站支付汽油时通常发生的情况。 API甚至可以让你格式化一个货币值,包括特定国家的货币符号。 ``` var formatterJPY = new Intl.NumberFormat('ja-JP', { maximumSignificantDigits: 2, style: 'currency', currency :'JPY }); console.log(formatterJPY.format(0.2233 + 0.1)); //记录"¥0.32" ``` 此外,它还允许各种格式的转换,如速度(如千米/小时)和体积(如_升)。在这个链接中,你可以找到Intl NumberFormat的所有可用选项。 然而,重要的是要注意这个功能的浏览器兼容性限制。由于它是一个标准,一些浏览器版本将不支持它的部分选项,如Internet Explorer和Safari。 对于这些情况,如果你愿意在这些网络浏览器上支持你的应用程序,那么欢迎使用后备方法。 ## Dinero.js, Currency.js, and Numeral.js 然而,社区总是会开发一些伟大的库来支持缺失的功能,如dinero.js、currency.js和numeral.js。 市场上还有更多可用的库,但我们将专注于这三个库,因为它们代表了使用货币格式化功能的开发者的很大比例。 ## Dinero.js Dinero.js是一个轻量级的、不可变的、可连锁的JavaScript库,它是为处理货币价值而开发的,可以实现全局设置、扩展的格式化/四舍五入选项、简单的货币转换,以及对国际货币的本地支持。 安装它就像运行一个命令一样容易。 ``` npm install dinero.js ``` 使用这个库的最大好处之一是它完全接受了Fowler的货币定义,这意味着它同时支持金额和货币值。 ``` const money = Dinero({ amount: 100, currency: 'USD' }) ``` 此外,它还提供了默认的方法来处理货币计算的问题。 ``` const tax = Dinero({ amount: 10, currency: 'USD' }) const result = money.subtract(tax) // 返回新的Dinero对象 console.log(result.getAmount()) //记录90 ``` 需要说明的是,Dinero.js并不单独处理分值。金额是以小的货币单位指定的,这取决于你使用的货币。如果你使用的是美元,那么钱就用美分来表示。 为了帮助格式化部分,它为我们提供了toFormat()方法,它接收一个带有你想格式化的货币模式的字符串。 ``` Dinero({ amount: 100 }).toFormat('$0,0') // logs "$1" Dinero({ amount: 100000 }).toFormat('$0,0.00') //记录"$1,000.00" ``` 你可以控制库如何处理这些格式。例如,如果你处理的货币有不同的指数(即超过两个小数位),你可以明确地定义精度,如下图所示。 ``` Dinero({ amount: 100000, precision: 3 }).toFormat('$0,0.000') // logs "100.000" Dinero({ amount: 100, currency: 'JPY', precision: 0 }).toFormat() // logs "¥100.00" ``` 也许它最大的特点之一是对其方法的可连锁支持,这导致了更好的可读性和代码维护。 ```` Dinero({ amount: 10000, currency: 'USD' }) .add(Dinero({ amount: 20000, currency: 'USD' })) .除以(2) .百分比(50) .toFormat() // logs "$75.00" ``` Dinero.js还提供了一种方法,通过其转换方法来设置本地或远程的汇率转换API。你可以从外部REST API获取交易所数据,也可以用JSON文件配置一个本地数据库,Dinero.js可以用它来执行转换。 ## Currency.js Currency.js是一个非常小的(只有1.14 kB)用于处理货币值的JavaScript库。 为了解决我们谈到的浮点问题,currency.js在幕后与整数一起工作,并确保小数点的精度始终正确。 要安装它,你只需要一个命令。 ``` npm install currency.js ``` 这个库可以比Dinero.js更不啰嗦,它将货币价值(无论是字符串、小数、数字还是货币)封装在其currency()对象中。 ``` currency(100).value // logs 100 ``` 这个API非常简洁明了,因为它也采用了可连锁的风格。 ``` currency(100) .add(currency("$200")) .divide(2) .multiply(0.5) // simulates percentage .format() // logs "$75.00" ``` 它还接受字符串参数,如货币值,并带有符号,如上图所示。format()方法则会返回一个对人类友好的货币格式。 然而,当涉及到国际化时,currency.js默认为美国地区。如果你愿意使用其他货币,必须做一些额外的工作。 ``` const USD = value => currency(value); const BRL = value => currency(value, { symbol: 'R$', decimal: ',', separator: '.' }); const JPY = value => currency(value, { precision: 0, symbol: '¥' }); console.log(USD(110.223).format()); // logs "$110.22" console.log(BRL(110.223).format()); // logs "R$110,22" console.log(JPY(110.223).format()); // logs "¥110" ``` Currency.js比Dinero.js更干净,这很好。然而,它没有一个内置的方法来执行汇率转换,所以如果你的应用程序需要这样做,要注意这个限制。 ## **Numeral.js** 正如库名所示,Numeral.js更像是一个通用的库,处理在JavaScript中格式化和操作一般的数字。 虽然它也可以操作货币值,但它提供了一个非常灵活的API来创建自定义格式。 要安装它,只需要一个命令。 ``` npm install numeral ``` 它的语法与currency.js非常相似,当把一个货币值封装到它的numer()对象中时。 ``` numeral(100).value() // logs 100 ``` 当涉及到格式化这些值时,它更接近于Dinero.js的语法。 ``` numeral(100).format('$0,0.00') //记录"$100.00" ``` 由于该库内置的国际化功能有限,你必须设置你的,以防需要新的货币系统。 ``` numeral.register('locale', 'es', { delimiters: { thousands: ' ', decimal: ',' }, currency: { symbol: '€' } }) numeral.locale('es') console.log(numeral(10000).format('$0,0.00')) // logs "€10 000,00" ``` 当涉及到可连锁模式时,numer.js也提供了我们所寻找的同样的可读性。 ``` const money = numeral(100) .add(200) .divide(2) .multiply(0.5) // simulates percentage .format('$0,0.00') // logs "$75.00" ``` 到目前为止,Numeral.js是我们列表中最灵活的处理数字的库。它的灵活性包括能够按照你的意愿创建地区和格式。然而,在使用它时要小心,因为它没有提供默认的方式来计算小数的零精度,比如说。 # 总结 在这篇博文中,我们已经探讨了在JavaScript中处理货币价值的一些最佳选择,无论是客户端还是后端应用程序。让我们回顾一下到目前为止所讨论的一些重要观点。 如果你要处理货币,千万不要使用语言中的浮点原始数字或Number包装对象。 相反,最好使用你的网络浏览器默认提供的Interm API。它更灵活,更安全,更智能。然而,要注意它的兼容性限制。 不管是哪一种,如果你能给你的应用包增加一点重量,在处理货币计算和/或转换时考虑采用所展示的库之一。 最后,一定要参考他们的官方文档和测试。他们中的大多数都提供了很好的测试,以帮助你了解每个库的优点和缺点,并选择最适合你需要的库。 标签: javascript, 货币, javascript类型
评论已关闭