js数字精度丢失、大数相加、Number.EPSILON、Number.isFinite

220 阅读3分钟

js为什么会存在数字精度丢失的问题,以及如何进行解决

举例:0.1 + 0.2 === 0.3 //false

计算机存储双精度浮点数需要首先把十进制转换成二进制的科学计数法形式

计算规则:符号位(1)+指数位(11)+指数位偏移量的二进制+小数部分(52) 共64位

在该规则下,会存在十进制小数无法完全准确表示为二进制小数,在转换过程中会造成计算误差,导致精度问题。

解决方案: 1、针对需要展示的数据

function strip(num: number, precision = 16) {
	//precision最大值为16
	const val = +parseFloat(num.toPrecision(precision))
	console.info(val)
	return val
}

strip(333.333)
strip(5555.111111123456789)

image.png

2、先转换成整数操作后再转换成浮点数

/** * 精确加法 */

function add(num1, num2) {
	const num1Digits = (num1.toString().split('.')[1] || '').length
	const num2Digits = (num2.toString().split('.')[1] || '').length
	const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits))
	return (num1 * baseNum + num2 * baseNum) / baseNum
}

3、第三方库 Math.js 或者BigDeciml.js

解释下 JavaScript 中的 Number.EPSILON

Number.EPSILON 静态数据表示1 与大于 1 的最小浮点数之间的差值 2-52,或大约 2.220446049250313e-16(chrome 浏览器打印的值)。

应用 1、相等测试

console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false

function equal(x, y) {
  return Math.abs(x - y) < Number.EPSILON;
}

const x = 0.2;
const y = 0.3;
const z = 0.1;
console.log(equal(x + z, y)); // true

实际上 EPSILON 指定了数字“1”的精确度

然而,对于任何具有更大数量级的算术运算,Number.EPSILON 是不适用的。如果你的数据数量级在 10^3 的范围,那么小数部分的精确度将远远小于Number.EPSILON

function equal(x, y) {
  return Math.abs(x - y) < Number.EPSILON;
}

const x = 1000.1;
const y = 1000.2;
const z = 2000.3;
console.log(x + y); // 2000.3000000000002;误差为 10^-13 而不是 10^-16
console.log(equal(x + y, z)); // false

此时,需要更大的容差, 2000 * Number.EPSILON 的乘积可以为此情况提供足够的容差

function equal(x, y, tolerance = Number.EPSILON) {
  return Math.abs(x - y) < tolerance;
}

const x = 1000.1;
const y = 1000.2;
const z = 2000.3;
console.log(equal(x + y, z, 2000 * Number.EPSILON)); // true

如何在 JavaScript 中判断一个数字是整数?

isFinite 只有类型为数字且为有限数的值才返回 true,而非数字的值始终返回 false。

isInteger 整数时返回true,浮点数返回false

请注意,由于ECMAScript 浮点数编码(IEEE-754)的精度限制。例如,5.0000000000000001 只与 5 相差 1e-16,这个差值太小了而无法表示。因此,5.0000000000000001 将使用与 5 相同的编码表示,从而使得 Number.isInteger(5.0000000000000001) 返回 true

const { isInteger } = Number //是否为整数 整数返回true
export function isNumber(v: unknown): v is number {
	return typeof v === 'number' && !Number.isNaN(v) && Number.isFinite(v)
}

export function isInt(v: unknown): boolean {
	return isNumber(v) && isInteger(v)
}

export function isFloat(v: unknown): boolean {
	return isNumber(v) && !isInteger(v)
}

解释下 NaN(Not a Number)在浮点数运算中的作用。

表示数学上未定义或不可表示的结果

  1. 0/0
  2. Infinity/Infinity 、Infinity - Infinity
  3. 非数学运算,比如对负数开平方根、对负数取对数等
  4. 使用未初始化的变量进行运算
  5. 当阶码(指数部分)全为1时,只要位数不全为0 就是NaN

# js超过Number最大值该如何处理(超大数 运算失去精度)

使用 BigInt

const bigInt1 = BigInt(9007199254740991)
const bigInt2 = BigInt(2)

const result1 = bigInt1 + bigInt2 // 9007199254740993n
console.log(result1) // 输出: 9007199254740993n

使用库

npm install bignumber.js

const BigNumber = require('bignumber.js');
 
const num1 = new BigNumber('9007199254740991');
const num2 = new BigNumber('2');
 
const result = num1.plus(num2); 
console.log(result.toString()); // 输出: 9007199254740993

字符串处理

/**
 *
 * @param {*} str1 字符串
 * @param {*} str2 字符串
 * @returns
 */
function addBigNumber(str1, str2) {
	const length1 = str1.length
	const length2 = str2.length
	const length = Math.max(length1, length2)
	const arr1 = str1
		.padStart(length, 0)
		.split('')
		.map(item => Number(item))
	const arr2 = str2
		.padStart(length, 0)
		.split('')
		.map(item => Number(item))
	let res = ''
	let flag = 0
	console.info(str1, str2)
	for (let i = length - 1; i >= 0; i--) {
		let result = arr1[i] + arr2[i] + flag
		flag = Math.floor(result / 10)
		res = `${result % 10}` + res
	}
	if (flag) {
		res = `${flag}` + res
	}
	return res
}
const str1 = '91112'
const str2 = '33231'
const result = addBigNumber(str1, str2)
console.log('结果', result)