加载中...
js浮点数计算
第1节:AI工具推荐
第2节:日期方法⏰
第3节:js浮点数计算
课文封面

“0.1+0.2=0.30000000000000004”?
为什么会出现js计算精度丢失?该如何解决?
无论是选择高效稳定的第三方库,还是手动实现精确算法,课文中均提供了代码示例与解析。

为什么会出现精度丢失?

由于 JavaScript 使用 IEEE 754 标准的 64 位双精度浮点型来表示数字,这种表示方法在处理小数时会出现精度问题。

精度丢失

方法一:使用decimal.js

decimal.js适用于精度问题比较多的项目,长期性解决精度问题。

特点

  • 整数和浮点数
  • 简单但功能齐全的 API
  • 复制 JavaScript 和对象的许多方法Number.prototypeMath
  • 还处理十六进制、二进制和八进制值
  • 比 Java 的 BigDecimal 的 JavaScript 版本更快、更小,并且可能更易于使用
  • 无依赖项
  • 广泛的平台兼容性:仅使用 JavaScript 1.5 (ECMAScript 3) 功能
  • 全面的文档和测试集
  • 在 math.js 的后台使用
  • 包括 TypeScript 声明文件:decimal.d.ts

安装decimal.js

npm install decimal.js

浮点数计算:加减乘除

// import Decimal from "decimal.js"; const Decimal = require('decimal.js') let num1 = new Decimal('0.1'); let num2 = new Decimal('0.2'); // 加法 let sum = num1.plus(num2); // "0.3" console.log(sum.toString()); // "0.3" // 减法 let difference = num1.minus(num2); // "-0.1" console.log(difference.toString()); // "-0.1" // 乘法 let product = num1.times(num2); // "0.02" console.log(product.toString()); // "0.02" // 除法 let quotient = num1.dividedBy(num2); // "0.5" console.log(quotient.toString()); // "0.5"

可以封装一个方法来便于调用

// import Decimal from "decimal.js"; const Decimal = require('decimal.js') function calculate(a, b, operator) { const decimalA = new Decimal(a); const decimalB = new Decimal(b); switch (operator) { case '+': return decimalA.plus(decimalB).toString(); case '-': return decimalA.minus(decimalB).toString(); case '*': return decimalA.times(decimalB).toString(); case '/': if (b === '0' || b === 0) { throw new Error('除数不能为零'); } return decimalA.dividedBy(decimalB).toString(); default: throw new Error('无效的操作符'); } } console.log(calculate('0.1', '0.2', '+')); // "0.3" console.log(calculate('1.0', '0.1', '-')); // "0.9" console.log(calculate('1.1', '2.2', '*')); // "2.42" console.log(calculate('1.0', '3', '/')); // "0.33333333333333333333"

浮点数比较

直接使用浮点数比较可能会因为精度问题导致错误的结果,而 decimal.js 提供了精确的比较方法。

let amount1 = new Decimal('0.1').plus('0.2'); // "0.3" let amount2 = new Decimal('0.3'); // 比较是否相等 console.log(amount1.equals(amount2)); // true // 比较大小 console.log(amount1.greaterThan(amount2)); // false console.log(amount1.lessThan(amount2)); // false

精度设置

// 全局设置精度5位(默认20位) Decimal.set({ precision: 5 }); let result = new Decimal('1').dividedBy('3'); // 1除以3 console.log(result.toString()); // "0.33333" // 局部设置精度(3位) let localResult = new Decimal('1').dividedBy('3').toDecimalPlaces(3); // 1除以3 console.log(localResult.toString()); // "0.333"

方法二:封装简单的方法来实现金额计算

可以先获取要计算的两个数的小数位数最大值,确定需要放大多少倍才能将两个数都转为整数,之后以整数进行计算,计算完成后还原相应倍数。
如果小数位过多(15位以上)时,还是建议使用三方库来完成计算。

// 获取两个数中小数位数的最大值 function getMaxDecimalPlaces(num1, num2) { const getDecimalPlaces = (num) => { if (!isFinite(num)) return 0; const match = ('' + num).match(/\.(\d+)$/); return match ? match[1].length : 0; }; return Math.max(getDecimalPlaces(num1), getDecimalPlaces(num2)); } // 统一的浮点数计算方法 function calculate(a, b, operator) { if (b === 0 && operator === '/') { throw new Error('除数不能为零'); } const decimalPlaces = getMaxDecimalPlaces(a, b); const factor = Math.pow(10, decimalPlaces); switch (operator) { case '+': return (Math.round(a * factor) + Math.round(b * factor)) / factor; case '-': return (Math.round(a * factor) - Math.round(b * factor)) / factor; case '*': const totalDecimalPlaces = getMaxDecimalPlaces(a, 0) + getMaxDecimalPlaces(b, 0); const productFactor = Math.pow(10, totalDecimalPlaces); return Math.round(a * b * productFactor) / productFactor; case '/': const decimalPlacesA = getMaxDecimalPlaces(a, 0); const decimalPlacesB = getMaxDecimalPlaces(b, 0); const divideFactorA = Math.pow(10, decimalPlacesA); const divideFactorB = Math.pow(10, decimalPlacesB); return Math.round((a * divideFactorA) / (b * divideFactorB)) / Math.pow(10, decimalPlacesA - decimalPlacesB); default: throw new Error('无效的操作符'); } } console.log(calculate(0.1, 0.2, '+')); // 0.3 console.log(calculate(0.3, 0.2, '-')); // 0.1 console.log(calculate(0.1, 0.2, '*')); // 0.02 console.log(calculate(0.3, 0.2, '/')); // 1.5