JS 大数值处理和金额格式化处理方案

0 阅读5分钟

点赞 + 关注 + 收藏 = 学会了

在做前端开发或者使用 n8n、dify 等工具时可能会跟数字打交道,可能会遇到下面这些需求:

  • 显示金额:1234567.89 → 1,234,567.89
  • 金额计算:0.1 + 0.2
  • 超大 ID:9007199254740993
  • 阿拉伯数字转中文金额:123456.78 → 壹拾贰万叁仟肆佰伍拾陆元柒角捌分

很多刚接触 JavaScript 的开发者会直接使用 Number 类型处理这些问题,但实际上这里面隐藏了不少

举个例子:

如果后端传给你的 JSON 里的长 ID 没加引号,前端在 JSON.parse 的一瞬间就已经把精度丢了。

const rawJson = '{"id": 9007199254740995}';
const parsed = JSON.parse(rawJson);
console.log(parsed.id.toString()); // "9007199254740996" 精度丢失了!!!

解决方案

  1. 后端改 String:最省心的办法。让后端把超长 ID 以字符串形式下发。
  2. 前端插件:如果后端不改,你可以使用 json-bigint 库来解析原始的 JSON 字符串。

数值是否安全?

在实际应用中,可以通过一些方法来胖段当前数值是否安全。

JS 提供的方法:

Number.isSafeInteger(9007199254740991) // true
Number.isSafeInteger(9007199254740992) // false

处理大数值的几种方案

在 JavaScript 里,普通数字类型是 Number(64位双精度浮点),它有一个安全整数范围:

  • 最大安全整数:Number.MAX_SAFE_INTEGER = 9007199254740991
  • 最小安全整数:Number.MIN_SAFE_INTEGER = -9007199254740991

超过这个范围就会出现 精度丢失问题,例如:

console.log(9007199254740991 + 1) // 9007199254740992
console.log(9007199254740991 + 2) // 9007199254740992  ❌ 精度丢失

如果你要处理 超过 Number 最大安全值的整数,有几种常见方案👇

方案1:BigInt

现代 JavaScript 提供了一种新的类型:BigInt

它可以表示任意大的整数。

const num = BigInt("9007199254740993")

console.log(num + 1n)
// 9007199254740994n

BigInt 的话,数字后面会跟着一个字母 n,看到它就能区分这个值和普通的 Number 类型不一样,这个需求可能会涉及很大的数值。

它还有一种简写方法,在赋值的时候不需要加引号括者数字,而是在数字后面加个 n

const num = 9007199254740993n

需要注意的是,BigInt 不能和 Number 混合运算!!!

1n + 1
// ❌ 报错

必须统一类型:

1n + BigInt(1)

金融计算为什么不能直接用 Number

一个经典问题:

0.1 + 0.2

# 结果是 0.30000000000000004

原因是浮点数精度问题。

金融系统一般有两种解决方案。

方案一:金额用“分”存储

例如:

123.45 元

存储为:12345

前端展示时再除以 100。

const amount = 12345 / 100

console.log(amount)
// 123.45

这种方式是比较老派的方法。

方案二:使用大数库

**MikeMcl 写了几个很出名的处理数字的JS库,比如:

我用 bignumber.js 演示一下。

安装 bignumber.js

npm install bignumber.js

在前端项目里引入:

import BigNumber from 'bignumber.js';

此时直接计算小数位的数值

const a = new BigNumber(0.1)
const b = new BigNumber(0.2)

a.plus(b).toString()
// 0.3

// 格式化,保留2位小数
a.plus(b).toFormat(2)
// 0.30

处理数值比较大的数据也没问题

// 1. 创建大数(建议始终传入字符串)
const x = new BigNumber('9007199254740995.123456789');
const y = new BigNumber('100');

// 2. 加减乘除
const res = x.plus(y);      // 加
const res2 = x.minus(y);    // 减
const res3 = x.times(y);    // 乘
const res4 = x.div(y);      // 除

console.log(res.toString()); // "9007199254741095.123456789" (精度完全保留)

格式化数值

在金融行业,金额的展示不仅关乎美观,更关乎准确性合规性。针对你提出的千分位转换、中文大写转换以及大数处理

千分位格式化

方案1:toLocaleString()

JavaScript 原生支持国际化格式化。

const amount = 123456789.56

amount.toLocaleString()
// 123,456,789.56

方案2:Intl.NumberFormat

金融项目比较推荐使用这个方案。

const formatter = new Intl.NumberFormat('zh-CN', {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
})

console.log(formatter.format(1234567.8))
// 1,234,567.80

如果想在金额前面加一个“钱”的符号,比如人民币就加个 ¥,可以这么写:

const formatter = new Intl.NumberFormat('zh-CN', {
  style: 'currency',
  currency: 'CNY'
})

console.log(formatter.format(123456))
// ¥123,456.00

如果要使用美元符 $ 就这么写:

const formatter = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
})

console.log(formatter.format(123456))
// $123,456.00

方案3:正则实现千分位(不推荐)

function formatNumber(num) {
  return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
}

console.log(formatNumber(123456789))
// 123,456,789

如果需要支持小数就这么写:

function formatMoney(num) {
  return num.toString().replace(/\d+/, function(n) {
    return n.replace(/(\d)(?=(\d{3})+$)/g, '$1,')
  })
}

金融系统一般不推荐这种方式,因为这种方式不支持国际化,不支持货币格式,容易出 bug。

数字转大写中文

在中文的金融系统中,经常需要展示:

123456.78
↓
壹拾贰万叁仟肆佰伍拾陆元柒角捌分

这个规则有点复杂,一般不建议自己实现。

我推荐使用开源库 nzhgithub.com/cnwhy/nzh)。

安装:

npm install nzh

使用:

import nzh from "nzh"

nzh.cn.encodeS(123456)
// 十二万三千四百五十六

nzh.cn.encodeB(123456.78)
// 壹拾贰万叁仟肆佰伍拾陆点柒捌

nzh.cn.toMoney(123456.78)
// 人民币壹拾贰万叁仟肆佰伍拾陆元柒角捌分

最后总结一下。

在 JavaScript 中处理金额和大数时,核心要记住三点:

1️⃣ 不要直接用 Number 做金融计算 2️⃣ 金额存储最好使用整数(分) 3️⃣ 展示时再进行格式化

只要遵循这三条原则,就可以避免绝大多数金额相关的 bug。

以上就是本文的全部内容了,还有疑问的话可以在评论区交流。

点赞 + 关注 + 收藏 = 学会了