js 加减乘除精度问题2

当我们使用

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 ✅
相关推荐
红目香薰2 小时前
Ascend C 算子:Sigmoid 函数原理深入解析与工程化构建及验证
c语言·开发语言·华为·华为云·昇腾·cann·modelarts
OTWOL2 小时前
C语言操作符终极揭秘:表达式求值秘籍
c语言·开发语言·c++
无巧不成书02182 小时前
Java 21 LTS 高级特性零基础通关:静态导入、项目目录规范、泛型全实战
java·开发语言·标准目录结构·泛型原理·类型安全实现
张np2 小时前
java框架和http调用接口的区别
java·开发语言·http
李日灐2 小时前
【优选算法3】二分查找经典算法面试题
开发语言·c++·后端·算法·面试·二分查找·双指针
ldybk2 小时前
教学vue
前端·javascript·vue.js
zzwq.2 小时前
PyMySQL 详解:从入门到实战,Python 操作 MySQL 一站式指南
开发语言·python
小松加哲2 小时前
MyBatis完整流程详解
java·开发语言·mybatis
Z1Jxxx2 小时前
C++ P1151 子数整数
开发语言·c++·算法