JS 数值原理

291 阅读3分钟

这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战

C、JAVA等语言有多种数值类型:short、int、long、float、double 等。

而 JavaScript 只有一种数值类型:number,简洁明了,但是也存在许多问题。

number 类型的实现借用自 IEEE 754 标准,是 64 位的浮点数类型,其中包含 1 位符号位、11 位指数位以及 53 位有效位数。

image.png

一个 number 数值的计算公式:符号位(正/负)* 有效位值 * (2 ** 指数位值)

需要注意的是有效位的范围:0.5<= 有效位数值 < 1.0。这样的话最高位始终是1,可以省略,能用的二进制位就多了一个。

下面可以写一个函数,用来分析数值类型的本质,该函数代码来自《JavaScript 悟道》。

function deconstruct(number) {
    let sign = 1;
    let coefficient = number;
    let exponent = 0;

    if (coefficient < 0) {
        coefficient = -coefficient;
        sign = -1;
    }

    if (Number.isFinite(number) && number !== 0) {
        exponent = -1128;
        let reduction = coefficient;
        
        while (reduction !== 0) {
            exponent += 1;
            reduction /= 2;
        }

        reduction = exponent;

        while (reduction > 0) {
            coefficient /= 2;
            reduction -= 1;
        }

        while (reduction < 0) {
            coefficient *= 2;
            reduction += 1;
        }
    }

    return {
        '符号位': sign,
        '有效位': coefficient,
        '有效位二进制': coefficient.toString(2),
        '指数位': exponent,
        '指数位二进制': exponent.toString(2),
        '数值': number
    }
}

看一下最大安全整数:

console.log(deconstruct(Number.MAX_SAFE_INTEGER));

// 输出如下:
{
  '符号位': 1,
  '有效位': 9007199254740991,
  '有效位二进制': '11111111111111111111111111111111111111111111111111111',
  '指数位': 0,
  '指数位二进制': '0',
  '数值': 9007199254740991
}

看一下 0.1+0.2 与 0.3的差别:

console.log(deconstruct(0.1));
console.log(deconstruct(0.2));
console.log(deconstruct(0.1+0.2));
console.log(deconstruct(0.3));

// 输出如下:
{
  '符号位': 1,
  '有效位': 7205759403792794,
  '有效位二进制': '11001100110011001100110011001100110011001100110011010',
  '指数位': -56,
  '指数位二进制': '-111000',
  '数值': 0.1
}
{
  '符号位': 1,
  '有效位': 7205759403792794,
  '有效位二进制': '11001100110011001100110011001100110011001100110011010',
  '指数位': -55,
  '指数位二进制': '-110111',
  '数值': 0.2
}
{
  '符号位': 1,
  '有效位': 10808639105689192,
  '有效位二进制': '100110011001100110011001100110011001100110011001101000',
  '指数位': -55,
  '指数位二进制': '-110111',
  '数值': 0.30000000000000004
}
{
  '符号位': 1,
  '有效位': 10808639105689190,
  '有效位二进制': '100110011001100110011001100110011001100110011001100110',
  '指数位': -55,
  '指数位二进制': '-110111',
  '数值': 0.3
}

可见 JavaScript 不擅长处理小数,所以 0.1+0.2 !== 0.3。在跟金钱相关的数值计算中要留意。

记住,JavaScript 无法精确表示大多数小数,带小数点的数值基本上都是有误差的!