面试题-js 超过 Number 最大值的数怎么处理?

261 阅读5分钟

在 JavaScript 中,Number 类型有一个最大值 Number.MAX_SAFE_INTEGER(值为 9007199254740991),当处理超过这个值的数时,会出现精度丢失的问题。为了处理超过 Number 最大值的数,JavaScript 提供了 BigInt 类型,同时也可以借助第三方库,下面为你详细介绍这些方法。

使用 BigInt 类型

BigInt 是 JavaScript 中的一种内置对象,它提供了一种方法来表示大于 2^53 - 1 的整数。可以通过在整数末尾添加 n 或者使用 BigInt() 构造函数来创建 BigInt 类型的值。

示例代码

// 创建 BigInt 类型的值
const bigNum1 = 9007199254740992n;
const bigNum2 = BigInt("9007199254740992");

// 进行加法运算
const result = bigNum1 + bigNum2;

// 输出结果
console.log(result); 
console.log(typeof result); 

代码解释

  1. 创建 BigInt 类型的值
    • bigNum1 通过在整数末尾添加 n 来创建 BigInt 类型的值。
    • bigNum2 使用 BigInt() 构造函数,传入一个字符串形式的大整数来创建 BigInt 类型的值。
  2. 进行加法运算:对两个 BigInt 类型的值进行加法运算,结果仍然是 BigInt 类型。
  3. 输出结果:使用 console.log() 输出运算结果和结果的类型。

使用第三方库

如果需要进行更复杂的数学运算,或者要兼容旧版本的 JavaScript 环境,可以使用第三方库,如 decimal.js

安装 decimal.js

npm install decimal.js

示例代码

const Decimal = require('decimal.js');

// 创建 Decimal 类型的值
const num1 = new Decimal('9007199254740992');
const num2 = new Decimal('9007199254740992');

// 进行加法运算
const result = num1.plus(num2);

// 输出结果
console.log(result.toString()); 

代码解释

  1. 引入 decimal.js:使用 require() 函数引入 decimal.js 库。
  2. 创建 Decimal 类型的值:使用 Decimal() 构造函数,传入一个字符串形式的大整数来创建 Decimal 类型的值。
  3. 进行加法运算:使用 plus() 方法对两个 Decimal 类型的值进行加法运算。
  4. 输出结果:使用 toString() 方法将结果转换为字符串并输出。

综上所述,对于超过 Number 最大值的数,推荐优先使用 BigInt 类型,因为它是 JavaScript 内置的类型,使用起来更加方便。如果需要进行更复杂的数学运算,可以考虑使用第三方库。

那么 decimal.js 的工作原理是什么

decimal.js 是一个用于在 JavaScript 中进行任意精度十进制算术运算的第三方库。下面详细介绍它的工作原理:

核心原理概述

decimal.js 的核心原理是通过自定义的数据结构和算法来存储和处理十进制数,而不是依赖 JavaScript 原生的 Number 类型。JavaScript 的 Number 类型基于 IEEE 754 双精度 64 位浮点数表示,这会导致在进行十进制运算时出现精度丢失的问题。decimal.js 则通过将数字以字符串的形式存储,并实现自己的算术运算逻辑,从而避免了这些精度问题。

具体工作机制

1. 数据存储

decimal.js 使用自定义的对象来存储十进制数,该对象包含以下几个重要属性:

  • s(符号):表示数字的符号,1 表示正数,-1 表示负数。
  • e(指数):表示数字的指数部分,用于确定小数点的位置。
  • d(数字数组):以数组的形式存储数字的每一位,数组中的每个元素代表一个十进制数字。

例如,数字 123.45 可能会被存储为:

{
    s: 1,
    e: 2,
    d: [1, 2, 3, 4, 5]
}

这里 s1 表示正数,e2 表示小数点应该在从右往左数第 2 位之后,d 数组存储了数字的每一位。

2. 构造函数

当你使用 new Decimal('123.45') 创建一个 Decimal 对象时,构造函数会执行以下步骤:

  • 解析输入字符串:将输入的字符串解析为符号、指数和数字数组。
  • 规范化数字:去除前导零和末尾零,并调整指数和数字数组。

3. 算术运算

decimal.js 实现了自己的算术运算逻辑,如加法、减法、乘法和除法等。以加法为例,其工作步骤如下:

  • 对齐小数点:通过调整指数和数字数组,使两个数字的小数点对齐。
  • 逐位相加:从最低位开始,逐位相加,并处理进位。
  • 规范化结果:去除结果中的前导零和末尾零,并调整指数和数字数组。

以下是一个简化的加法运算示例:

// 简化的 Decimal 类示例
class Decimal {
    constructor(str) {
        // 解析输入字符串
        this.s = str[0] === '-' ? -1 : 1;
        const parts = str.replace(/^-/, '').split('.');
        const integerPart = parts[0];
        const fractionalPart = parts[1] || '';
        this.e = integerPart.length - 1;
        this.d = (integerPart + fractionalPart).split('').map(Number);
    }

    plus(other) {
        // 对齐小数点
        let maxE = Math.max(this.e, other.e);
        let thisDigits = [...this.d];
        let otherDigits = [...other.d];
        while (this.e < maxE) {
            thisDigits.unshift(0);
            this.e++;
        }
        while (other.e < maxE) {
            otherDigits.unshift(0);
            other.e++;
        }

        // 逐位相加
        let carry = 0;
        let resultDigits = [];
        let i = Math.max(thisDigits.length, otherDigits.length) - 1;
        while (i >= 0 || carry > 0) {
            let sum = carry;
            if (i < thisDigits.length) {
                sum += thisDigits[i];
            }
            if (i < otherDigits.length) {
                sum += otherDigits[i];
            }
            resultDigits.unshift(sum % 10);
            carry = Math.floor(sum / 10);
            i--;
        }

        // 规范化结果
        while (resultDigits.length > 1 && resultDigits[0] === 0) {
            resultDigits.shift();
            maxE--;
        }
        return new Decimal(resultDigits.join('')).setExponent(maxE);
    }

    setExponent(e) {
        this.e = e;
        return this;
    }

    toString() {
        let str = this.d.join('');
        if (this.e < str.length - 1) {
            str = str.slice(0, this.e + 1) + '.' + str.slice(this.e + 1);
        }
        if (this.s === -1) {
            str = '-' + str;
        }
        return str;
    }
}

// 使用示例
const num1 = new Decimal('123.45');
const num2 = new Decimal('67.89');
const result = num1.plus(num2);
console.log(result.toString()); 

4. 舍入和精度控制

decimal.js 还支持舍入和精度控制。你可以通过设置 Decimal.config() 中的 precisionrounding 选项来控制运算结果的精度和舍入方式。在进行运算时,库会根据这些设置对结果进行舍入处理。

综上所述,decimal.js 通过自定义的数据结构和算法,避免了 JavaScript 原生 Number 类型的精度问题,实现了任意精度的十进制算术运算。