导航


HTML

CSS

JavaScript

浏览器 & 网络

版本管理

框架

构建工具

TypeScript

性能优化

软实力

算法

UI、组件库

Node

冷门技能

现象

console.log(.1 + .2 === .3); // false

⭐️ JS 进制互转

(10).toString(2); // '1010'
parseInt('1010', 2); // 10

整数十进制转二进制

除以 进制,取余,倒序

10 转 2

10 / 2 = 5, rem = 0
5 / 2 = 2, rem = 1
2 / 2 = 1, rem = 0
1 / 2 = 0, rem = 1

最终反着读: 1010

小数十进制转二进制

(0.1).toString(2)
'0.0001100110011001100110011001100110011001100110011001101'

乘以 进制,取整,正序

.1 * 2 = .2, rem = 0
.2 * 2 = .4, rem = 0
.4 * 2 = .8, rem = 0
.8 * 2 = .6, rem = 1
.6 * 2 = .2, rem = 1
.2 * 2 = .4, rem = 0
.4 * 2 = .8, rem = 0
.8 * 2 = .6, rem = 1
.6 * 2 = .2, rem = 1

最终:小数位不变 0. 后边是多少取多少: 0.0001100110011...

更多案例

.1 + .2
// 0.30000000000000004
2.3 + 2.4
// 4.699999999999999
1.0 - .9
// 0.09999999999999998
3 * .3
// 0.8999999999999999
1.21 / 1.1
// 1.0999999999999999

精度保留、四舍五入

Number.toPrecision

作用:不四舍五入,只保留精度(从第一个不为0的位数开始算精度)

let numObj = 5.123456

console.log(numObj.toPrecision())    // logs '5.123456'
console.log(numObj.toPrecision(5))   // logs '5.1235'
console.log(numObj.toPrecision(2))   // logs '5.1'
console.log(numObj.toPrecision(1))   // logs '5'
console.log(numObj.toPrecision(20))   // logs '5.1234560000000000102'
console.log(numObj.toPrecision(19))   // logs '5.123456000000000010'
console.log(numObj.toPrecision(18))   // logs '5.12345600000000001'
console.log(numObj.toPrecision(17))   // logs '5.1234560000000000'
console.log(numObj.toPrecision(16))   // logs '5.123456000000000'
console.log((0.2).toPrecision(3)); // 0.200

Number.toFixed

作用:四舍五入,保留小数点位数

let numObj = 12345.6789

console.log(numObj.toFixed()); // log 12346
console.log(numObj.toFixed(1)); // log 12345.7
console.log(numObj.toFixed(6)); // 12345.678900

// 特殊情况
numObj = 0.105;
console.log(numObj.toFixed()); // log 0
console.log(numObj.toFixed(1)); // log 0.1
console.log(numObj.toFixed(2)); // log 0.10 (这里精度有问题,没有进一)
console.log(numObj.toFixed(6)); // log 0.105000
console.log(numObj.toFixed(16)); // log 0.1050000000000000
console.log(numObj.toFixed(17)); // log 0.10500000000000000
console.log(numObj.toFixed(18)); // log 0.104999999999999996(出现精度问题了)

原理

浮点数精度问题

浮点数在转二进制时,由于磁盘物理空间大小是有限的,所以无法存储一个无限的小数,导致浮点数转二进制有精度差

Number

64位 双精度浮点数

组成:

// JS中所能表示的最大数
Number.MAX_VALUE === 1.7976931348623157e+308 === 2 ** 1023 * 1.999999999999999;

// JS中的最大安全数
Number.MAX_SAFE_INTEGER === 9007199254740991 === 2 ** 53 - 1; (这里多乘的2是符号位)
所以JS中最大精度数是 9007199254740991 位数:16

// JS中的最小安全数
Number.MIN_SAFE_INTEGER === -9007199254740991 === -(2 ** 53 - 1);

解决方案

⭐️ toPrecision + parseFloat + toFixed

const formatMoney = (money, digit = 2) => {
  if (money == null) return '';
  return parseFloat(money.toPrecision(12), 10).toFixed(digit);
}

console.log(0.1 + 0.2); // 0.30
console.log(4.1 - 4);   // 0.10
console.log(16.56 * 3); // 49.68

Math.pow + Math.round + toFixed

function formatMoney(amount, precision = 2) {
  // 确保精度为非负整数
  if (precision < 0 || !Number.isInteger(precision)) {
    throw new Error('Precision must be a non-negative integer');
  }
  
  // 将金额转换为字符串,并根据精度截取小数部分
  const factor = Math.pow(10, precision);
  const formattedAmount = Math.round(amount * factor) / factor;
  
  return formattedAmount.toFixed(precision);
}

// 使用示例
console.log(formatMoney(0.1 + 0.2)); // 输出 "0.30"
console.log(formatMoney(0.1 + 0.2, 3)); // 输出 "0.300"
console.log(formatMoney(1234.5678, 2)); // 输出 "1234.57"
console.log(formatMoney(1234.5678, 0)); // 输出 "1235"

BigInt

const a = BigInt(2);
console.log(a); // 2n

const b = 233333n;
console.log(typeof b); // bigint

对比:

console.log(2 ** 53 === 2 ** 53 + 1); // true

const previousMaxSafe = BigInt(Number.MAX_SAFE_INTEGER) // ↪ 9007199254740991n

const maxPlusOne = previousMaxSafe + 1n;
console.log(previousMaxSafe === maxPlusOne); // false

Number.EPSILON

x = 0.2;
y = 0.3;
z = 0.1;
equal = (Math.abs(x - y + z) < Number.EPSILON);
console.log(equal); // true

原理(polyfill)

if (Number.EPSILON === undefined) {
  Number.EPSILON = Math.pow(2, -52); // JS最小精度值
}

三方库