numeral vs dinero.js vs currency.js vs currency-formatter
前端货币处理与格式化库的技术选型指南
numeraldinero.jscurrency.jscurrency-formatter类似的npm包:

前端货币处理与格式化库的技术选型指南

currency-formatter、currency.js、dinero.js 和 numeral 都是用于处理数字格式化和货币计算的 JavaScript 库,但它们的侧重点截然不同。numeral 是一个通用的数字格式化工具,支持多种格式但已停止维护;currency-formatter 专注于根据 ISO 标准快速生成货币字符串;currency.js 旨在解决浮点数精度问题,提供轻量级的货币计算能力;而 dinero.js 则是一个功能完备的不可变货币对象库,专为复杂的金融逻辑和高精度计算设计。开发者需要根据项目是仅需展示格式化、需要简单计算,还是需要严格的金融级精度和领域模型来选择相应的工具。

npm下载趋势

3 年

GitHub Stars 排名

统计详情

npm包名称
下载量
Stars
大小
Issues
发布时间
License
numeral2,369,5959,698-34410 年前MIT
dinero.js733,5176,803858 kB317 个月前MIT
currency.js730,8423,375-655 年前MIT
currency-formatter0212-155 年前MIT

前端货币处理深度对比:从格式化到金融级精度

在前端开发中,处理货币不仅仅是加个美元符号那么简单。我们面临着三个核心挑战:本地化格式化(不同国家的符号和分隔符)、浮点数精度陷阱(JavaScript 原生 Number 类型的致命缺陷)以及业务逻辑复杂性(舍入规则、货币分配)。

currency-formatter、currency.js、dinero.js 和 numeral 代表了四种不同的解决思路。本文将深入剖析它们的内部机制,通过代码实战帮你做出正确的架构决策。

⚠️ 重要警示:关于 numeral 的状态

在深入技术细节前,必须明确一点:numeral 已不再维护。它的最后一次更新远在多年前,社区已普遍转向其他方案。虽然它的 API 设计曾经非常优雅,支持极其丰富的格式化模板,但在现代技术栈中引入一个“死库”会带来安全隐患和构建工具兼容性问题。

// numeral (已过时,不推荐在新项目使用)
// 这种写法在现代打包工具中可能需要额外的 shim 配置
var numeral = require('numeral');
numeral(1000.234).format('$0,0.00'); // 输出:$1,000.23

建议:如果你的项目仅需格式化,请迁移至 currency-formatter 或原生 Intl.NumberFormat;若需计算,请选择 currency.js 或 dinero.js。

🎨 场景一:纯展示格式化 (Formatting Only)

当你的需求只是将后端传来的数字(通常是整数分或浮点数)展示给用户看时,性能和解耦是关键。

currency-formatter:专注格式的轻量选手

currency-formatter 不做任何计算,它只做一件事:把数字变成字符串。它依赖 ISO 4217 标准数据,能正确处理各种奇怪的货币符号位置和小数位数(如日元没有小数位)。

import { format } from 'currency-formatter';

// 自动处理日元无小数位
format(5000, { code: 'JPY' }); 
// 输出:"¥5,000"

// 自定义欧元格式
format(1234.56, { code: 'EUR', locale: 'de-DE' }); 
// 输出:"1.234,56 €" (符合德国习惯)

优势:体积极小,无副作用,专门解决 Intl.NumberFormat 在某些旧环境或特定配置下不够直观的问题。 劣势:无法处理计算,输入必须是数字,如果传入浮点数进行复杂运算后再格式化,精度问题依然存在。

numeral (历史参考)

作为对比,看看 numeral 曾经如何处理。它的模板系统非常强大,但过于灵活导致容易出错。

// numeral (已过时)
numeral(1234567).format('0,0.00 $'); // 完全自定义模板
// 输出:"1,234,567.00 $"

对比结论:在现代项目中,currency-formatter 比 numeral 更安全、更专注。如果只需要格式化,甚至可以直接使用浏览器原生的 Intl.NumberFormat,无需额外依赖。

🧮 场景二:轻量级计算 (Lightweight Calculation)

当需要在购物车、表单预览等场景进行简单的加减乘除,且必须避免 0.1 + 0.2 = 0.30000000000000004 这种尴尬时。

currency.js:以开发者体验为导向

currency.js 的核心思想是“让货币像数字一样好用,但没有精度问题”。它在内部将金额转换为整数(分)进行计算,但在 API 层面暴露为浮点数对象,支持链式调用。

import currency from 'currency.js';

const price = currency(10.50);
const tax = currency(2.10);

// 链式计算,自动处理精度
const total = price.add(tax).multiply(2).subtract(1);

console.log(total.value); // 获取原始数字 (24.00)
console.log(total.format()); // 直接格式化 "$24.00"

技术细节:

  • 内部存储:使用整数运算避免浮点误差。
  • API 风格:类似 jQuery 的链式调用,非常直观。
  • 容错性:构造函数能自动处理字符串输入 currency("10.50")。

适用场景:电商购物车、简单的费用计算器、SaaS 订阅价格预览。 局限性:不支持复杂的金融规则(如银行家舍入法的高级配置),也不支持货币兑换逻辑。

🏦 场景三:金融级精度与领域模型 (Financial Grade)

当你的应用涉及账务系统、多方金额分配、复杂的税务逻辑或需要严格审计时,简单的库已经不够用了。你需要的是一个“领域模型”。

dinero.js:不可变的货币对象

dinero.js 不仅仅是一个工具库,它是一个完整的货币领域模型。它强制你使用“分”或最小货币单位作为整数输入,从根本上杜绝了浮点数。它的所有操作都是不可变的(Immutability),即每次操作都会返回一个新的 Dinero 对象,而不是修改原值。

import Dinero from 'dinero.js';

// 必须使用整数(美分)初始化,杜绝浮点歧义
const price = Dinero({ amount: 1050, currency: 'USD' }); // $10.50
const tax = Dinero({ amount: 210, currency: 'USD' });    // $2.10

// 计算返回新对象,原对象不变
const total = price.add(tax);

// 复杂的舍入控制
const halfDollar = Dinero({ amount: 1235, currency: 'USD' });
const rounded = halfDollar.toUnit('dollar', { round: 'HALF_UP' });

// 货币分配(解决分钱除不尽的问题)
// 将 $10.03 分配给 3 个人,不会出现丢失 1 分钱的情况
const [alice, bob, charlie] = Dinero({ amount: 1003, currency: 'USD' }).allocate([33, 33, 34]);

核心优势:

  1. 安全性:强制整数输入,API 层面无法创建不精确的货币对象。
  2. 不可变性:在复杂的状态管理(如 Redux)中非常安全,不会出现意外修改。
  3. 高级功能:内置 allocate(分配)、compare(比较)、hasSameCurrency(同币种校验)等金融专用方法。
  4. 链式与函数式:支持完美的函数式编程风格。

代价:

  • 学习曲线:需要理解“最小货币单位”概念,不能直接传 10.5。
  • 体积:比 currency.js 大得多。
  • 互操作性:与后端交互时,需要频繁地在“整数分”和“浮点元”之间转换(建议后端也统一返回分)。

🥊 深度对比:核心差异点

1. 数据输入与精度控制

这是最本质的区别。currency.js 试图对你输入的浮点数进行“补救”,而 dinero.js 直接禁止你输入浮点数。

// currency.js: 尝试修复浮点数,但输入仍是浮点
const c1 = currency(0.1).add(0.2); 
// 内部转换:10 分 + 20 分 = 30 分 -> 0.3 (成功)

// dinero.js: 强制整数,从源头消灭问题
const d1 = Dinero({ amount: 10, currency: 'USD' }).add(
  Dinero({ amount: 20, currency: 'USD' })
);
// 必须显式构造,无法误传 0.1

分析:在简单场景下,currency.js 的 DX(开发体验)更好;但在涉及大量数据导入或用户自由输入的场景,dinero.js 的严格模式能防止更多潜在 Bug。

2. 货币分配算法 (Allocation)

在金融场景中,将总金额按比例分配给多方时,常因除不尽导致“丢失几分钱”。这是检验库专业度的试金石。

// dinero.js: 内置专业的分配算法
const amounts = Dinero({ amount: 100, currency: 'USD' }).allocate([50, 50]);
// 结果:[50, 50]

const amounts2 = Dinero({ amount: 101, currency: 'USD' }).allocate([50, 50]);
// 结果:[51, 50] (自动处理余数,保证总和不变)

// currency.js: 无此功能,需手动实现
// 开发者必须自己写逻辑处理余数,极易出错
const total = 101;
const part1 = Math.floor(total * 0.5);
const part2 = total - part1; // 手动补丁

结论:只要涉及“分账”、“佣金计算”、“税费分摊”,dinero.js 是唯一可靠的选择。

3. 可变性 (Mutability)

// currency.js: 可变操作 (部分方法) 或 返回新对象混合
// 需注意链式调用是否改变了原引用,文档虽声称返回新对象,但内部状态管理较简单
const cart = currency(100);
const updated = cart.add(10); 

// dinero.js: 严格不可变
const original = Dinero({ amount: 100, currency: 'USD' });
const modified = original.add(10);

console.log(original.getAmount()); // 依然是 100,绝对安全

在 React 或 Vue 等响应式框架中,不可变数据能显著减少调试难度,确保状态流转清晰。

📊 选型决策矩阵

特性currency-formattercurrency.jsdinero.jsnumeral
主要用途纯格式化展示轻量级计算 + 格式化金融级计算 + 领域模型通用格式化 (已过时)
精度处理无 (依赖输入)内部转整数,对外屏蔽强制整数输入,严格无 (浮点数)
API 风格函数式链式 (jQuery 风)链式 (函数式风)链式
不可变性N/A部分严格不可变否
高级功能多 Locale 支持基础四则运算分配、比较、兑换、舍入策略丰富但过时
维护状态✅ 活跃✅ 活跃✅ 活跃 (v2 重构中)❌ 停止维护
推荐场景商品列表、报表购物车、表单账务系统、支付核心无 (请避免)

💡 架构师建议

  1. 对于绝大多数内容型网站或简单电商: 组合使用 原生 Intl.NumberFormat (用于格式化) + 简单的整数运算 (后端返回分,前端直接加减)。如果不想处理整数转换,使用 currency.js 是最快的落地方案,它能以最小的成本解决精度痛点。

  2. 对于 Fintech、SaaS 计费、复杂 ERP 系统: 不要犹豫,直接上 dinero.js。虽然前期需要适应“整数输入”和“不可变对象”的模式,但它提供的 allocate 方法和严格的类型约束,会在项目后期节省大量的调试时间和资金风险。它的代码即文档,明确表达了货币的领域逻辑。

  3. 关于 numeral 的迁移: 如果老项目中使用了 numeral,建议制定计划逐步替换。格式化逻辑可替换为 currency-formatter 或原生 API;计算逻辑必须重构为 currency.js 或 dinero.js,否则精度 Bug 随时可能爆发。

最后总结:

  • 只要展示?选 currency-formatter。
  • 简单算算?选 currency.js。
  • 涉及钱袋子核心逻辑?选 dinero.js。
  • numeral?让它留在历史里吧。

如何选择: numeral vs dinero.js vs currency.js vs currency-formatter

  • numeral:

    仅建议在维护旧项目时使用 numeral,新项目中应避免引入。虽然它支持非常灵活的数字格式化(包括百分比、字节、时间等),但该库已长期停止维护,存在未修复的 bug 且不支持现代 ES 模块标准。如果需要通用数字格式化,建议寻找更新的替代方案或自行编写格式化函数。

  • dinero.js:

    对于涉及核心金融逻辑、高并发交易或对数据一致性要求极高的企业级应用,必须选择 dinero.js。它采用不可变数据模式,提供丰富的领域方法(如分配、比较、舍入),并严格遵循金融计算标准。虽然学习曲线较陡且包体积较大,但它能从根本上杜绝精度丢失和状态突变带来的 bug,是构建可靠财务系统的首选。

  • currency.js:

    当你需要在浏览器端进行简单的货币加减乘除运算,且希望避免 JavaScript 原生浮点数精度错误(如 0.1 + 0.2 !== 0.3)时,应选择 currency.js。它提供了类似 jQuery 的链式调用 API,上手成本低,适合中小型项目或原型开发。不过,它的功能集相对基础,不适合处理复杂的税务计算或货币兑换逻辑。

  • currency-formatter:

    如果你的需求仅仅是将数字转换为符合特定地区标准的货币字符串(如 '$1,234.56'),而不涉及复杂的数学运算,currency-formatter 是最佳选择。它轻量且专注于格式化,API 简单直接,适合用于电商列表页、报表展示等只读场景。但请注意,它不负责解决浮点数精度问题,计算逻辑需自行处理。

numeral的README

Numeral.js

A javascript library for formatting and manipulating numbers.

Website and documentation

Travis Build Status

Master Build Status

Develop Build Status

NPM

NPM

#CDNJS

CDNJS

Contributing

Important: Please create your branch from and submit pull requests to the develop branch. All pull requests must include the appropriate tests.

  1. Fork the library

  2. Install grunt

  3. Run npm install to install dependencies

  4. Create a new branch from develop

  5. Add your tests to the files in /tests

  6. To test your tests, run grunt

  7. When all your tests are passing, run grunt dist to compile and minify all files

  8. Submit a pull request to the develop branch.

Formats

Formats now exist in their own files and act more or less as plugins. Check out the bytes format for an example of how to create one.

Locales

When naming locale files use the ISO 639-1 language codes supplemented by ISO 3166-1 country codes when necessary.

Locale translations will not be merged without unit tests.

See the english unit tests for an example.

Changelog

2.0.6

Bug fix: Multi letter currency symbols and spacing

Added: Formatting of numbers with leading zeros

New format: Basic Point

Option: Added scalePercentBy100 (default: true) option to turn on/off scaling percentages

2.0.4

Bug fix: Incorrect abbreviations for values rounded up #187

Bug fix: Signed currency is inconsistent #89

2.0.2

Bug fix: Updated module definitions

2.0.1

Bug fix: Fixed regression for webpack/browserify/rollup

2.0.0

2.0.0 brings a lot of breaking changes and a reorganization of the repo, but also simplifies the api as well as the creating of custom formats.

Breaking change / Feature: All formats are now separate files. This makes it easy to create custom formats, and will also allow for custom builds with only certain formats. (Note: The built numeral.js still contains all formats in the repo).

Breaking change / Feature: All formats and locales are now loaded using numeral.register(type, name, {})

Breaking change: All language now renamed to locale and standardized to all lowercase filenames

Breaking change: The locale function no longer loads locales, it only sets the current locale

Breaking change: The unformat function has been removed numeral().unformat(string) and now happens on numeral init numeral(string)

Breaking change / Feature: Bytes are now formatted as: b (base 1000) and ib (base 1024)

Breaking change: numeral(NaN) is now treated the same as numeral(null) and no longer throws an error

Feature: Exponential format using e+ or e-

Bug fix: Update to floating point helpers (Note: Numeral does not fix JS floating point errors, but look to our tests to see that it covers quite a few cases.)

1.5.6

Bug fix: numeral converts strings to numbers

Bug fix: Null values return same as 0

1.5.5

Contained breaking changes, recommended to use 1.5.6

Bug fix: Switch bytes back to b and change iecBinary to ib, and calculate both using 1024 for backwards compatibility

1.5.4

Contained breaking changes, recommended to use 1.5.6

Tests: Changed all tests to use Mocha and Chai

Tests: Added browser tests for Chrome, Firefox, and IE using saucelabs

Added reset function to reset numeral to default options

Added nullFormat option

Update reduce polyfill

Added Binary bytes

Bug fix: Fixes problem with many optional decimals

1.5.3

Added currency symbol to optionally appear before negative sign / open paren

Added float precision math support

Added specification of abbreviation in thousands, millions, billions

1.5.2

Bug fix: Unformat should pass through if given a number

Added a mechanism to control rounding behaviour

Added languageData() for getting and setting language props at runtime

1.5.1

Bug fix: Make sure values aren't changed during formatting

1.5.0

Add defaultFormat(). numeral().format() uses the default to format if no string is provided

.unformat() returns 0 when passed no string

Added languages.js that contains all languages

Bug fix: Fix bug while unformatting ordinals

Add format option to always show signed value

Added ability to instantiate numeral with a string value of a number

1.4.9

Bug fix: Fix bug while unformatting ordinals

1.4.8

Bug fix: Throw error if language is not defined

1.4.7

Bug fix: Fix typo for trillion

1.4.6

Bug fix: remove ' from unformatting regex that was causing an error with fr-ch.js

1.4.5

Add zeroFormat() function that accepts a string for custom formating of zeros

Add valueOf() function

Chain functionality to language function

Make all minified files have the same .min.js filename ending

1.4.1

Bug fix: Bytes not formatting correctly

1.4.0

Add optional format for all decimals

1.3.4

Remove AMD module id. (This is encouraged by require.js to make the module more portable, and keep it from creating a global)

1.3.3

AMD define() compatibility.

1.3.2

Bug fix: Formatting some numbers results in the wrong value. Issue #21

1.3.1

Bug fix: Minor fix to unformatting parser

1.3.0

Add support for spaces before/after $, a, o, b in a format string

Bug fix: Fix unformat for languages that use '.' in ordinals

Bug fix: Fix round up floating numbers with no precision correctly.

Bug fix: Fix currency signs at the end in unformat

1.2.6

Add support for optional decimal places

1.2.5

Add support for appending currency symbol

1.2.4

Add support for humanized filesizes

1.2.3

Bug Fix: Fix unformatting for languages that use '.' as thousands delimiter

1.2.2

Changed language definition property 'money' to 'currency'

1.2.1

Bug fix: Fix unformatting non-negative abbreviations

1.2.0

Add language support

Update testing for to include languages

1.1.0

Add Tests

Bug fix: Fix difference returning negative values

1.0.4

Bug fix: Non negative numbers were displaying as negative when using parentheses

1.0.3

Add ordinal formatting using 'o' in the format

1.0.2

Add clone functionality

1.0.1

Added abbreviations for thousands and millions using 'a' in the format

1.0.0

Initial release

Acknowlegements

Numeral.js, while less complex, was inspired by and heavily borrowed from Moment.js

License

Numeral.js is freely distributable under the terms of the MIT license.

Copyright (c) 2012 Adam Draper

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.