当我们使用
bignumber, mathjs, Decimal 等处理 数字加减乘除的时候 会遇到 即使使用了,还是有精度问题
例如
const a = new Decimal(val1);
const b = new Decimal(val2);
const sum = a.plus(b);
结果不是我们期望的
? why
因为Decimal 要求参数是字符串, 当然他能够接受一直数值类型的. 但有没有想过这个数值本身精度就有问题呢
例如 后端接口传过来的一个变量是个数字val1, 我们要求这个数字加上 0.1
const a = new Decimal(val1);
const b = new Decimal('0.1');
const sum = a.plus(b);
当js处理时 这个数字精度就有问题了
解决方法
最佳:推动后端改为返回字符串 { "amount": "123.45" }
次选:如果数值在安全范围内(如两位小数的金额),可尝试:
javascript
在这里插入代码片
javascript
// 仅适用于固定小数位(如货币)
const safeStr = (backendNumber).toFixed(2); // 先转为精确字符串
const d = new Decimal(safeStr);
toFixed 和 toPrecision 区别
| 特性 | .toFixed(n) |
.toPrecision(n) |
|---|---|---|
| 控制什么? | 小数点后的位数(固定小数位) | 整个数字的有效数字位数 |
| 返回类型 | 字符串 | 字符串 |
| 是否四舍五入? | 是 | 是 |
| 可能用科学计数法? | 否(始终定点表示) | 是(当数字太大或太小时) |
| 典型用途 | 货币显示(如保留2位小数) | 高精度数值的紧凑表示 |
javascript
// decimalUtils.js
import { Decimal } from 'decimal.js';
// 默认配置
const DEFAULT_CONFIG = {
precision: 64, // 全局有效数字精度
defaultDecimals: 2, // 默认显示小数位数
numberPrecision: 15 // 处理 number 时的有效数字位数
};
// 设置全局精度
Decimal.set({ precision: DEFAULT_CONFIG.precision });
/**
* 安全创建 Decimal 实例(内部使用)
*/
function _createDecimal(value, options) {
const config = Object.assign({}, DEFAULT_CONFIG, options);
if (value == null) return new Decimal(0);
if (value instanceof Decimal) return value;
if (typeof value === 'string') {
const cleanStr = value.trim();
if (cleanStr === '') return new Decimal(0);
return new Decimal(cleanStr);
}
if (typeof value === 'number') {
if (!isFinite(value)) {
throw new Error(`Invalid number: ${value}`);
}
// 优先使用 fixedDecimals 修复(适用于金额等已知小数位场景)
if (config.fixedDecimals != null) {
const factor = Math.pow(10, config.fixedDecimals);
const rounded = Math.round(value * factor + Number.EPSILON);
return new Decimal(rounded).div(factor);
}
// 回退到 toPrecision(15)
const safeStr = value.toPrecision(DEFAULT_CONFIG.numberPrecision);
return new Decimal(safeStr);
}
throw new Error(`Unsupported type for Decimal: ${typeof value}`);
}
/**
* 高精度加法 - 返回字符串
*/
function add(a, b, decimals) {
const result = _createDecimal(a).plus(_createDecimal(b));
return result.toFixed(decimals || DEFAULT_CONFIG.defaultDecimals, Decimal.ROUND_HALF_UP);
}
/**
* 高精度减法 - 返回字符串
*/
function subtract(a, b, decimals) {
const result = _createDecimal(a).minus(_createDecimal(b));
return result.toFixed(decimals || DEFAULT_CONFIG.defaultDecimals, Decimal.ROUND_HALF_UP);
}
/**
* 高精度乘法 - 返回字符串
*/
function multiply(a, b, decimals) {
const result = _createDecimal(a).times(_createDecimal(b));
return result.toFixed(decimals || DEFAULT_CONFIG.defaultDecimals, Decimal.ROUND_HALF_UP);
}
/**
* 高精度除法 - 返回字符串
*/
function divide(a, b, decimals) {
const divisor = _createDecimal(b);
if (divisor.isZero()) {
throw new Error('Division by zero');
}
const result = _createDecimal(a).div(divisor);
return result.toFixed(decimals || DEFAULT_CONFIG.defaultDecimals, Decimal.ROUND_HALF_UP);
}
/**
* 格式化任意值为固定小数位字符串
*/
function format(value, decimals) {
const d = _createDecimal(value);
return d.toFixed(decimals || DEFAULT_CONFIG.defaultDecimals, Decimal.ROUND_HALF_UP);
}
/**
* 比较大小(仍返回数字,因为比较结果不需要字符串)
* @returns -1 (a < b), 0 (a === b), 1 (a > b)
*/
function compare(a, b) {
return _createDecimal(a).comparedTo(_createDecimal(b));
}
// 导出所有方法
export {
add,
subtract,
multiply,
divide,
format,
compare
};
math.js 和 decimal 区别
| 特性 | decimal.js | math.js |
|---|---|---|
| 核心目标 | 高精度十进制算术(解决 0.1 + 0.2 ≠ 0.3 问题) |
全能数学计算引擎(支持符号计算、矩阵、复数、表达式解析等) |
| 适用场景 | 金融计算、金额处理、需要精确小数的业务 | 科学计算、工程模拟、教育工具、复杂公式求解 |
| 精度保证 | ✅ 任意精度十进制(可配置) | ❌ 默认使用 JavaScript 原生 number(有浮点误差),但可集成 decimal.js |
| 功能 | decimal.js | math.js |
|---|---|---|
| 基础四则运算 | ✅ | ✅ |
| 高精度小数 | ✅ | ❌(除非集成 Decimal) |
| 矩阵运算 | ❌ | ✅ |
| 复数运算 | ❌ | ✅ |
表达式解析(如 '2+3*4') |
❌ | ✅ |
| 符号计算(代数) | ❌ | ✅ |
| 单位换算(kg → lb) | ❌ | ✅ |
| 统计函数 | ❌ | ✅ |
| 三角函数/对数等 | ⚠️ 有限支持 | ✅ 完整支持 |
我喜欢的 number-precision (因为不会遇到特别高精度的东西)
javascript
import NP from 'number-precision';
/**
* 安全地将 number 转换为字符串,避免精度表示问题
*/
function _safeToString(val) {
if (typeof val !== 'number' || !isFinite(val)) {
return String(val);
}
if (Number.isInteger(val)) {
return String(val);
}
// 使用 toPrecision(15) 保留 JS number 的最大安全精度
return parseFloat(val.toPrecision(15)).toString();
}
/**
* 高精度加法
* @param {...(number|string)} args
* @returns {number}
*/
export function add(...args) {
const processed = args.map(arg =>
typeof arg === 'number' ? _safeToString(arg) : String(arg)
);
return NP.plus(...processed);
}
/**
* 高精度减法
* @param {number|string} a
* @param {number|string} b
* @returns {number}
*/
export function sub(a, b) {
return NP.minus(
typeof a === 'number' ? _safeToString(a) : String(a),
typeof b === 'number' ? _safeToString(b) : String(b)
);
}
/**
* 高精度乘法
* @param {number|string} a
* @param {number|string} b
* @returns {number}
*/
export function mul(a, b) {
return NP.times(
typeof a === 'number' ? _safeToString(a) : String(a),
typeof b === 'number' ? _safeToString(b) : String(b)
);
}
/**
* 高精度除法
* @param {number|string} a
* @param {number|string} b
* @returns {number}
*/
export function div(a, b) {
return NP.divide(
typeof a === 'number' ? _safeToString(a) : String(a),
typeof b === 'number' ? _safeToString(b) : String(b)
);
}
/**
* 高精度四舍五入
* @param {number|string} num
* @param {number} decimals - 保留小数位数
* @returns {number}
*/
export function round(num, decimals) {
return NP.round(
typeof num === 'number' ? _safeToString(num) : String(num),
decimals
);
}
javascript
import { add, sub, mul, div, round } from './math-safe';
// 示例:处理后端传来的 number(假设在安全范围内)
const price = 19.9;
const taxRate = 0.1;
const tax = mul(price, taxRate); // 1.99
const total = add(price, tax); // 21.89
const final = round(total, 2); // 21.89
console.log(add(0.1, 0.2)); // 0.3 ✅
console.log(sub(1.0, 0.9)); // 0.1 ✅