用Dinero.js在JavaScript中存储和检索精确的货币价值
程序员从事各种属于不同商业领域的网络应用,如医疗、金融、电子商务、云计算、业务流程管理和教育。这些行业经常操纵货币价值,随着越来越多的金融企业将其服务转化为基于云的应用程序,这些基于云的应用程序显然需要能够精确地表示、操纵和存储金融数额。
即使你的网络应用程序可能没有明确存在以执行财务计算,但它可能为计费目的计算货币金额。这些类型的货币计算应该是比较准确的,因为错误会导致参与这些金融交易的各方在现实世界中的损失。
因此,我们需要选择最合适的数据类型来存储财务数字。一些编程语言,如C#和Java,原生支持具有任意点小数类型的货币值。然而,JavaScript的基于硬件的浮动数据类型并不适合货币应用。它没有货币值的原生数据类型,而且它只能让你把小数存储为双精度的浮点数(IEEE 754)。
在这篇文章中,我将解释如何使用Dinero.js库在JavaScript和TypeScript中用对象更精确地存储、操作和表示货币价值。
为什么float数据类型不适合存储货币价值?
一般来说,人们使用base-ten数字系统进行计算。基-10数字系统使用的数字从0
到9
。因此,我们计算的所有数字都是由这些数字或这些数字的组合组成的。
我们的数字计算经常产生精确的小数,也可以产生无穷无尽的小数。例如,1/3
分数会产生一个无尽的0.3333…
。另一方面,1/4
分数会产生精确的0.25
。
正如我们所知,计算机只能理解一个称为二进制数系统的二进制数系统,它由一个较高的电压值(1
)和一个较低的电压值(0
)组成。计算机内部会将十进制值存储为二进制数,与十进制系统一样,十进制到二进制的转换也会产生无尽的二进制分数。
例如,1/10
十进制分数会产生一个无尽的0.00011…
二进制数字。另一方面,1/2
十进制分数创造了精确的0.1
二进制数字。
现在你可能已经知道了,当二进制表示法太长或无尽时,float数据类型存储了给定十进制值的近似值。当你检索存储的浮动值进行计算时,它就不是你先前存储的精确值了。
这些转换错误可能会在汇总的金额中产生相当大的差距,并可能导致四舍五入的错误。因此,原生的JavaScript float数据类型不适合用于货币值。
开始使用Dinero.js
Dinero.js是基于Martin Fowler的货币模式建立的,这在他的《企业应用架构模式》一书中有解释。书中解释说,软件开发人员需要用OOP(面向对象编程)模式来存储货币价值。该模式促使开发者使用一个带有货币和金额的类来存储货币价值。
因此,Dinero.js让程序员在JavaScript中使用对象精确地存储、操作和呈现货币价值。它支持世界上所有活跃货币的定义,包括像马达加斯加阿里亚里这样的非十进制货币,并作为一组孤立的模块,这样你就可以在生产应用中只包括所需的模块,以保持更小的捆绑规模。
Dinero的V2 alpha版本最近已经发布,它将很快成为稳定版本。在此期间,你可以在Node.js、浏览器和所有流行的前端框架中使用Dinero。
要开始使用,请用npm或Yarn包管理器安装该库。
npm install dinero.js@alpha
# or
yarn add dinero.js@alpha
另外,你也可以导入Dinero的最小化版本,直接在网页浏览器中使用。在本教程中,我将通过使用ECMAScript模块(MJS)来演示Dinero与Node的结合。
基本概念
首先,让我们尝试用Dinero存储一个价值为75.50
。假设这个值带有一个未知的货币。把下面的代码保存到一个名为index.mjs
的文件中,然后用node index.mjs
命令运行它。
import { dinero } from 'dinero.js';
const price = dinero({amount: 7550, scale: 2});
console.log(price.toJSON());
我们需要明白,我们不是在处理浮点数或整数值。每个货币金额都将成为Dinero API的一个Dinero对象。
上面的片段用7550
创建了一个Dinero对象,因为我们需要提供一个整数作为金额。我们还要求Dinero使用一个比例因子2
,因为实际价格是75.50
。最后一行记录了Dinero对象的结构,如下图所示。
币种
我们创建的上述Dinero对象没有任何货币。如前所述,该库支持世界上几乎所有活跃的货币,但为了方便起见,让我们尝试存储75.99
USD。
在使用预定义货币之前,你需要先安装Dinero货币包。
npm install @dinero.js/currencies@alpha
# or
yarn add @dinero.js/currencies@alpha
安装完货币包后,运行下面的代码段。
import { dinero } from 'dinero.js';
import { USD } from '@dinero.js/currencies';
const price = dinero({amount: 7599, currency: USD})
console.log(price.toJSON());
现在,我们可以看到,刻度是根据货币的指数属性自动定义的。
格式化
我们可以使用Dinero对象进行内部货币表示,但我们需要在网络应用中以纯文本形式显示它们。因此,我们需要通过使用库的格式化函数将Dinero对象转换为典型的十进制数字。
看看下面的Dinero对象是如何转换为小数点后一位的小数值的。
import { dinero, toUnit, down } from 'dinero.js';
import { USD } from '@dinero.js/currencies';
const price = dinero({amount: 7599, currency: USD});
const priceDecimal = toUnit(price, { digits: 1, round: down });
console.log(priceDecimal); // 75.9
此外,你可以通过toFormat
函数得到小数值的字符串表示,如下图所示。
import { dinero, toFormat } from 'dinero.js';
import { USD } from '@dinero.js/currencies';
const transformer = ({ amount, currency }) => `${currency.code} ${amount}`;
const price = dinero({ amount: 7599, currency: USD });
console.log(toFormat(price, transformer)); // "USD 75.99"
存储和检索
Dinero对象像其他通用的JavaScript对象一样被存储在计算机的物理内存中。很明显,我们需要把它们保存到数据库中,以便长期保存。
Dinero提供了序列化和反序列化的API函数,我们可以使用toSnapshot
函数来获得一个可存储的原始JSON对象。
import { dinero, toSnapshot } from 'dinero.js';
import { USD } from '@dinero.js/currencies';
const price = dinero({ amount: 5000, currency: USD });
const rawData = toSnapshot(price);
console.log(rawData); // Store rawData in database
我们可以像往常一样用dinero
方法将存储的原始JSON对象转换回Dinero对象。
import { dinero, toSnapshot } from 'dinero.js';
import { USD } from '@dinero.js/currencies';
const price = dinero({ amount: 5000, currency: USD });
const rawData = toSnapshot(price);
// Store rawData in database
const priceFromDb = dinero(rawData);
console.log(priceFromDb.toJSON());
运算操作
Dinero有几个API突变函数,允许你操作定义在Dinero对象中的货币金额。通过这些函数,我们可以进行算术运算,如加、乘、减等。请看下面的代码片断,了解一些算术运算的例子。
import { dinero,
toUnit,
down,
add,
subtract,
multiply
} from 'dinero.js';
import { USD } from '@dinero.js/currencies';
const d1 = dinero({amount: 7599, currency: USD});
const d2 = dinero({amount: 1599, currency: USD});
console.log(`d1 = ${toUnit(d1)}`);
console.log(`d2 = ${toUnit(d1)}`);
let ans;
ans = add(d1, d2);
console.log(`d1 + d2 = ${toUnit(ans)}`);
ans = subtract(d1, d2);
console.log(`d1 - d2 = ${toUnit(ans)}`);
ans = multiply(d1, 2);
console.log(`d1 x 2 = ${toUnit(ans)}`);
货币除法
在现实世界的交易中,我们经常需要除以货币金额。在某些情况下,这些除法可能有点复杂。例如,假设你的网络应用程序处理一个特定服务的两个交易的总付款额为USD100.53
。你可能会猜测会有两笔相同的付款,金额为USD50.265
。但你不可能把一个实体硬币分成两部分,对吗?
即使你可以把这个值四舍五入为USD50.27
,你的收费也比实际值高。为了解决这个问题,Dinero提供了allocate
方法来分配货币价值,没有任何除法错误。
下面的例子将USD100.53
分配成两个金额。
import { dinero, allocate, toUnit } from 'dinero.js';
import { USD } from '@dinero.js/currencies';
const price = dinero({ amount: 10053, currency: USD });
const [d1, d2] = allocate(price, [1, 1]);
console.log(toUnit(d1), toUnit(d2)); // 50.27 50.26
上面的代码片段创建了两个Dinero对象:一个是50.27
,一个是50.26
。因此,除法的结果是两个可以正确转换为货币的金额。
算术比较
Dinero有很多函数用于比较。例如,我们可以使用equal
方法来检查两个Dinero对象是否相等。
import { dinero, equal } from 'dinero.js';
import { USD } from '@dinero.js/currencies';
const d1 = dinero({ amount: 5022, currency: USD });
const d2 = dinero({ amount: 5022, currency: USD });
if(equal(d1, d2)) {
console.log("d1 equals d2");
}
同样,也有API函数来检查大于、小于等。Dinero API文档中有所有支持的比较函数的定义。
TypeScript支持
Dinero.js是用TypeScript编写的,它原生支持TypeScript开发环境。
将Dinero作为一个依赖项添加到你的TypeScript项目中。之后,你的包管理器也会获取所有TypeScript定义。
看看下面这个Dinero TypeScript的例子。
import { Dinero, dinero, toUnit, down } from 'dinero.js';
import { USD } from '@dinero.js/currencies';
const price: Dinero = dinero({ amount: 50011, currency: USD });
const priceDecimal: number = toUnit(price, { digits: 2, round: down });
console.log(priceDecimal);
你可以从这个StackBlitz演示中查看上述例子的完整源代码。
Dinero.js与其他现有解决方案的比较
Dinero.js最初是在大约一年前发布的。在那之前,程序员们使用不同的类似Dinero的库来表示JavaScript中的货币金额。一些程序员在不使用库的情况下使用了其他方法,比如Martin Fowler的货币模式,它将货币价值存储为整数,但他们随后需要从头开始实现一切。Money$afe库提供了与Dinero类似的API,但它没有实现预定义的货币,而且文档中说它还没有准备好生产。
big.js是另一个流行的库,用于操作任意精度的十进制数字。然而,它在存储小数值而不出现四舍五入错误方面的解决方案有点通用,而且我们不能期望有很多与钱有关的功能,比如预定义货币或等分。然而,big.js是处理加密货币的一个很好的解决方案--甚至Dinero也在其FAQ页面上推荐big.js用于加密货币。
总结
程序员总是需要使用正确的数据类型来存储金融价值。否则,网络应用程序可能会存储、处理和显示不正确的金融数额,网络应用程序所有者可能会因此而面临客户的不满问题,甚至是法律诉讼。
幸运的是,Dinero是一个JavaScript库,它以模块化和不可变的API为JavaScript中的货币操作提供了一个安全的领域模型。
The postStore and retrieve precise monetary values in JavaScript with Dinero.jsappeared first onLogRocket Blog.
转载自:https://juejin.cn/post/7067852145604689927