☰
Dinero.js 货币(Currency)完全指南:Code、Base 与 Exponent 的深入解析
2026/10/9 2:28:01 网站建设 项目流程
  • 金融科技

【免费下载链接】dinero.js

Create, calculate, and format money in JavaScript and TypeScript

项目地址:https://gitcode.com/gh_mirrors/di/dinero.js
点击查看免费下载

导读

货币(Currency)是创建 Dinero 对象所必需的三大领域数据(amount、currency、scale)之一,它决定了金额以何种货币单位、何种细分粒度被解释。本文以 docs/core-concepts/currency.md 为核心脉络,深入讲解 Dinero.js 货币对象的三个构成要素——代码(code)、进制(base)、指数(exponent),并结合作者仓库源码说明内置 ISO 4217 货币的实现方式、自定义货币的正确写法,以及 TypeScript 编译时货币类型安全机制。读完本文,你将能正确为任何现实或虚构货币建模,并安全地在运行时使用内置货币。

货币:创建 Dinero 对象的三要素之一

在 Dinero.js 中,一个货币对象由三部分组成:

  • 代码(code):货币的唯一标识符;
  • 进制(base / radix):表示货币最小细分单位所需的进制(通常是 10,也可以是一个表示多级细分的数组);
  • 指数(exponent):货币与其最小细分单位之间的十进制关系。

从源码看,这三个字段被收敛为一个精确的类型定义,见 packages/dinero.js/src/currencies/types/DineroCurrency.ts:

export type DineroCurrency<TAmount, TCurrency extends string = string> = { readonly code: TCurrency; readonly base: TAmount | readonly TAmount[]; readonly exponent: TAmount; };

注意TAmount与TCurrency两个泛型参数:TAmount代表数值类型(number默认,也可以是bigint或第三方大数库),TCurrency代表货币代码的字面量类型(默认string,配合as const可收窄为'USD'这样的字面量,从而实现编译时类型安全)。所有属性均为readonly,避免货币定义在运行时被意外篡改。

货币代码(Currency code)

货币代码是货币的唯一标识符。按惯例,它通常是三个字母或数字。以国家法定货币为例,ISO 4217 代码的前两位字母来自 ISO 3166-1 alpha-2 国家代码,第三位通常是货币名称的首字母。

// United States dollar const USD = { code: 'USD', // ... };

当货币不指向特定国家(如欧元)或不属于传统货币(如加密货币)时,代码可以采用不那么标准但更易记忆的命名方案。关键在于:代码的选择取决于你的应用场景,唯一硬性要求是唯一性。例如仓库示例项目 examples/cart-react/src/lib/money.ts 中通过currencyFor(code)将字符串代码映射为对应货币对象,正是依赖代码的唯一性来完成查找。

货币进制(Currency base / radix)

货币进制是表示货币最小细分单位所需的进制(即进制基数)。大多数流通货币是十进制的,进制为 10:

const USD = { code: 'USD', base: 10, // ... };

但仍有非十进制货币在流通,例如 毛里塔尼亚乌吉亚(MRU,进制 5)和 马达加斯加阿里亚里:

// Mauritanian ouguiya const MRU = { code: 'MRU', base: 5, // ... };

多级细分:用数组表达进制

有些货币存在多级细分单位。例如十进制化之前的英镑:1 英镑 = 20 先令,1 先令 = 12 便士。虚构货币中也有类似例子,《哈利·波特》里的加隆:1 加隆 = 17 西可,1 西可 = 29 纳特。

Dinero.js 允许用数组逐级指定每一级细分:

// Pre-decimal Great Britain pound sterling const GBP = { code: 'GBP', base: [20, 12], exponent: 1, }; // Great Britain wizarding currency (Harry Potter universe) const GBW = { code: 'GBW', base: [17, 29], exponent: 1, };

::: info 使用非十进制货币时,应把指数(exponent)设为1。 :::

数组形式的base在底层是如何被处理的?看 packages/dinero.js/src/core/utils/computeBase.ts 的computeBase实现:

export function computeBase<TAmount>(calculator: DineroCalculator<TAmount>) { return (base: TAmount | readonly TAmount[]) => { if (isArray(base)) { return base.reduce((acc, curr) => calculator.multiply(acc, curr)); } return base; }; }

即:当base是数组时,将其逐项相乘得到整体进制(如[20, 12]计算为 240),并且所有运算都经由DineroCalculator抽象层完成——这意味着无论你使用number、bigint还是第三方大数实现,进制计算都保持同一套语义。

货币指数(Currency exponent)

货币指数表达的是货币与其最小细分单位之间的十进制关系。例如 1 美元 = 100 美分,100 是 10 的 2 次方,所以美元的指数为 2:

const USD = { code: 'USD', base: 10, exponent: 2, };

一个更直观的理解方式:指数就是小数点后的位数。USD 保留两位小数(分),指数为 2;JPY 无小数位,指数为 0。

// Japanese yen const JPY = { code: 'JPY', base: 10, exponent: 0, };

当货币没有最小细分单位(如日元)时,指数应为 0,此时可以在金额(amount)中用整数直接表达主单位数量。

指数与 scale 的关系

当你向 Dinero 对象传入 scale 时,scale 会覆盖货币指数——这直接影响你该如何指定金额。这可以从 packages/dinero.js/src/core/types/DineroOptions.ts 中看出,scale是创建 Dinero 对象时的可选参数:

export type DineroOptions<TAmount, TCurrency extends string = string> = { readonly amount: TAmount; readonly currency: DineroCurrency<TAmount, TCurrency>; readonly scale?: TAmount; };

大多数情况下你无需手动指定scale,它会默认取货币指数。但在涉及税率、折扣等分数值时,手动提高 scale 可以保留中间计算精度(详见 scale 概念文档 中 5.5% VAT 的示例)。

使用内置货币

Dinero.js 开箱即用地提供了 ISO 4217 货币对象,通过dinero.js/currencies子路径导出。

安装 Dinero.js 之后(参见 快速开始),即可导入货币:

import { dinero } from 'dinero.js'; import { USD, EUR } from 'dinero.js/currencies'; const d1 = dinero({ amount: 1000, currency: USD }); const d2 = dinero({ amount: 1000, currency: EUR });

如果你使用 bigint 变体,则从dinero.js/bigint/currencies导入:

import { dinero } from 'dinero.js/bigint'; import { USD, EUR } from 'dinero.js/bigint/currencies'; const d1 = dinero({ amount: 1000n, currency: USD }); const d2 = dinero({ amount: 1000n, currency: EUR });

两个入口的实际差异在源码中一目了然:number 变体的内置货币定义于 packages/dinero.js/src/currencies/iso4217.ts(共 1504 行,覆盖全部 ISO 4217 货币),每个货币都形如:

export const USD = { code: 'USD', base: 10, exponent: 2, } as const satisfies DineroCurrency<number, 'USD'>;

而 bigint 变体定义于 packages/dinero.js/src/bigint/currencies/iso4217.ts,数值字段全部以n结尾:

export const AED: DineroCurrency<bigint> = { code: 'AED', base: 10n, exponent: 2n, };

两个子路径均通过各自的 index.ts 统一导出(export * from './iso4217'; export * from './types';)。

版本稳定性警告

Dinero.js 持续追踪 ISO 4217 标准,并在修订发布时更新货币数据。这意味着当你升级 Dinero.js 时,货币可能被新增、移除或修改属性(如指数、代码)。iso4217.ts 文件头部的注释也明确提示了这一点。

::: warning货币数据可能随 Dinero.js 版本变化。如果你需要稳定性,请在包管理器中锁定 Dinero.js 版本;也可以自行定义货币对象以完全掌控。若在运行时按代码查找货币,务必校验代码是否存在——参考 如何按代码查找货币。 :::

实践中,来自 API 或数据库的货币代码是运行时字符串,需要使用命名空间导入按代码查找并校验:

import * as currencies from 'dinero.js/currencies'; function getCurrency(code: string) { if (!(code in currencies)) { throw new Error(`Unknown currency code: ${code}`); } return currencies[code as keyof typeof currencies]; }

同样的写法也适用于dinero.js/bigint/currencies(详见 FAQ 文档)。

创建自定义货币

如果dinero.js/currencies中没有你需要的货币(如加密货币、虚构货币、历史货币),可以自行构建货币对象:

const FRF = { code: 'FRF', base: 10, exponent: 2, };

TypeScript 用户可以实现DineroCurrency类型。它接收泛型参数TAmount(代表数值类型,默认为number):

import type { DineroCurrency } from 'dinero.js'; const FRF: DineroCurrency<number> = { code: 'FRF', base: 10, exponent: 2, };

编译时货币类型安全

要开启编译时货币类型安全,使用as const satisfies保留货币代码的字面量类型:

import type { DineroCurrency } from 'dinero.js'; const FRF = { code: 'FRF', base: 10, exponent: 2, } as const satisfies DineroCurrency<number, 'FRF'>;

as const让code的类型从string收窄为字面量'FRF',satisfies则校验对象符合DineroCurrency形状。这样一来,TypeScript 就能在编译期拦截货币不匹配的错误(例如对 USD 和 EUR 执行add):

import { dinero, add } from 'dinero.js'; import { USD, EUR } from 'dinero.js/currencies'; const d1 = dinero({ amount: 500, currency: USD }); // Dinero<number, 'USD'> const d3 = dinero({ amount: 100, currency: EUR }); // Dinero<number, 'EUR'> add(d1, d1); // OK add(d1, d3); // Type error: 'EUR' is not assignable to 'USD'

内置的 ISO 4217 货币已经以这种方式完成类型标注(见上文iso4217.ts中as const satisfies DineroCurrency<number, 'USD'>的写法)。TCurrency类型参数默认是string,因此未使用类型化货币的既有代码无需改动即可继续工作(保持向后兼容),而运行时校验(如haveSameCurrency)在任何情况下仍然生效。

货币建模的进阶应用

掌握货币三要素之后,可以进一步探索与之紧密相关的主题:

  • 货币类型安全:add、subtract、equal、compare、convert等操作如何在类型层面强制同币种约束,以及类型在快照(toSnapshot)和格式化回调(toDecimal)中的流转。
  • 非十进制货币的格式化:如何用toUnits将单细分(如古希腊德拉克马,base: 6)与多细分(如base: [30, 16]的游戏内货币)货币格式化为人类可读的文本。
  • 加密货币支持:加密货币通常具有较高的指数,可能超出安全整数范围,建议配合bigint或第三方大数库使用;dinero.js/currencies出于稳定性考虑不提供现成的加密货币实现,需要自行实现DineroCurrency类型。
  • 精度与大数:了解 bigint 变体与自定义数值类型的接入方式。
  • 金额(amount):理解金额如何依据货币的指数与进制在最小细分单位中表达。

货币的建模质量直接决定了金额计算、比较与格式化的正确性。只要把握住 code 的唯一性、base 的进制(含多级细分数组)与 exponent 的小数位语义这三个要点,你就能在 Dinero.js 中自如地为任意货币系统建模。

  • 金融科技

【免费下载链接】dinero.js

Create, calculate, and format money in JavaScript and TypeScript

项目地址:https://gitcode.com/gh_mirrors/di/dinero.js
点击查看免费下载
上一篇:system-design-101 数据管理六大模式详解:从旁路缓存到分片的实战指南
下一篇:BlockSuite 块树操作全解:从 doc.addBlock 到编辑器内的 Selection、Service 与 Commands

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询