前端必备:深入剖析数字操作工具类(NumberUtils)

前端必备:深入剖析 NumberUtils 数字操作工具类

一、引言

在前端开发中,涉及数字的运算操作无处不在,从简单的购物车金额计算,到复杂的金融数据处理、图表数据生成等场景,都需要精准且可靠的数字运算支持。然而,JavaScript 原生的数字类型在处理大数字以及浮点数精度时存在一些局限性,常常会引发意想不到的错误。今天,就为大家带来一款功能强大的 NumberUtils 数字操作工具类,它能帮我们轻松应对这些棘手问题,实现高精度、稳定的数字运算。

二、工具类概述

NumberUtils 是一个专注于数字操作的工具类,提供了诸如加法、减法、乘法、除法以及数字比较等一系列常用的数字运算方法。它不仅能够处理常规的数字运算,更具备处理大数字运算的能力,有效避免了 JavaScript 原生运算在大数字场景下的精度丢失问题。同时,还支持对运算结果的精度控制和舍入模式设定,满足多样化的业务需求。

三、核心功能详解

1. 加法运算 - add 方法

add(...args: (number | string | NumberOptions)[]): string | number {
    // 取选项和数字
    const options = this.getLastOptions(args);
    const numbers = this.filterNumbers(args);
    // 参数校验
    if (numbers.length === 0) {
        return 0;
    }
    // 获取所有数字的最大小数位数
    const maxDecimals = this.getMaxDecimalLength(numbers);
    // 如果有任何一个数需要大数处理,则全部使用大数处理
    if (numbers.some(n => this.needBigIntHandling(n))) {
        try {
            const sum = numbers.reduce((acc, curr) => {
                const [intPart, decPart = ""] = curr.toString().split(".");
                const paddedDec = decPart.padEnd(maxDecimals, "0");
                const bigNum = BigInt(intPart + paddedDec);
                return acc + bigNum;
            }, BigInt(0));
            // 格式化结果
            const result = this.formatBigDecimal(sum, maxDecimals);
            return this.applyPrecision(
                result,
                options.precision,
                options.roundingMode
            );
        } catch (error) {
            throw new Error(`大数计算错误: ${error.message}`);
        }
    }

    // 普通数字处理
    try {
        // 将所有数字转换为整数进行计算,避免浮点数精度问题
        const scaleFactor = Math.pow(10, maxDecimals);
        const sum = numbers.reduce((acc: number, curr) => {
            // 先将字符串转换为数字
            const numValue = typeof curr === "string"? parseFloat(curr) : curr;
            if (isNaN(numValue)) {
                throw new Error(`无效的数字: ${curr}`);
            }
            const scale = Math.pow(
                10,
                maxDecimals - this.getDecimalLength(numValue)
            );
            // 确保 acc 是 number 类型
            return Number(acc) + numValue * scale;
        }, 0);
        // 还原小数位并格式化
        const result = sum / scaleFactor;
        return this.applyPrecision(
            result,
            options.precision,
            options.roundingMode
        );
    } catch (error) {
        throw new Error(`计算错误: ${error.message}`);
    }
}
  • 该方法接收任意数量的参数,可以是数字、字符串形式的数字或者包含精度、舍入模式设置的 NumberOptions 对象。首先,它会从参数列表中提取出数字和选项,对参数进行校验,如果没有有效数字则返回 0。
  • 接着,判断是否有需要大数处理的数字。如果存在,将所有数字转换为 BigInt 类型进行大数加法运算,先按照最大小数位数进行补 0 处理,计算完成后再格式化结果,并根据指定的精度和舍入模式进行调整。
  • 若都是普通数字,为避免浮点数精度问题,先将所有数字转换为整数,按照最大小数位数统一缩放后进行加法运算,最后还原小数位并应用精度和舍入规则。

例如:

// 普通加法
NumberUtils.add(0.1, 0.2); // 返回 0.3

// 大数加法
NumberUtils.add('9007199254740991', '1'); // 返回 '9007199254740992'

// 指定精度
NumberUtils.add(0.1234, 0.1234, { precision: 3 }); // 返回 0.247

// 多个数字相加
NumberUtils.add(0.1, 0.2, 0.3); // 返回 0.6

// 混合大数和普通数字
NumberUtils.add('9007199254740991', 1.234, { precision: 3 }); // 返回 '9007199254740992.234'

2. 减法运算 - subtract 方法

subtract(
    minuend: number | string,
    subtrahend: number | string,
    options?: NumberOptions
): string | number {
    if (
        this.needBigIntHandling(minuend) ||
        this.needBigIntHandling(subtrahend)
    ) {
        const bigMinuend = BigInt(this.toInteger(minuend));
        const bigSubtrahend = BigInt(this.toInteger(subtrahend));
        return (bigMinuend - bigSubtrahend).toString();
    }
    const result = Number(minuend) - Number(subtrahend);
    return this.formatNumber(result, options?.precision);
}
  • 对于减法,方法首先判断被减数和减数是否需要大数处理。若需要,将它们转换为 BigInt 类型进行减法运算,结果以字符串形式返回。
  • 如果是普通数字,直接进行减法运算,然后根据指定的精度对结果进行格式化。

例如:

// 普通减法
NumberUtils.subtract(0.3, 0.1); // 返回 0.2

// 大数减法
NumberUtils.subtract('9007199254740991', '1'); // 返回 '9007199254740990'

3. 乘法运算 - multiply 方法

multiply(
    multiplicand: number | string,
    multiplier: number | string,
    options?: NumberOptions
): string | number {
    if (
        this.needBigIntHandling(multiplicand) ||
        this.needBigIntHandling(multiplier)
    ) {
        const bigMultiplicand = BigInt(this.toInteger(multiplicand));
        const bigMultiplier = BigInt(this.toInteger(multiplier));
        return (bigMultiplicand * bigMultiplier).toString();
    }
    const num1 = Number(multiplicand);
    const num2 = Number(multiplier);
    const m = this.getDecimalLength(num1) + this.getDecimalLength(num2);
    const result =
        (num1 *
            Math.pow(10, this.getDecimalLength(num1)) *
            (num2 * Math.pow(10, this.getDecimalLength(num2)))) /
        Math.pow(10, m);
    return this.formatNumber(result, options?.precision);
}
  • 乘法运算同样先判断参与运算的数字是否需要大数处理。若是,转换为 BigInt 类型相乘后返回字符串结果。
  • 对于普通数字,先计算两个数字小数位的总和 m,将数字转换为整数相乘后再除以 10m 次方,还原小数位,最后根据精度要求格式化结果。

例如:

// 普通乘法
NumberUtils.multiply(0.1, 0.2);  // 返回 0.02

// 大数乘法
NumberUtils.multiply('9007199254740991', '2');  // 返回 '18014398509481982'

4. 除法运算 - divide 方法

divide(
    dividend: number | string,
    divisor: number | string,
    options?: NumberOptions
): string | number {
    if (Number(divisor) === 0) {
        throw new Error("除数不能为0");
    }
    if (this.needBigIntHandling(dividend) || this.needBigIntHandling(divisor)) {
        const precision = options?.precision?? this.DEFAULT_PRECISION;
        const scale = BigInt(Math.pow(10, precision));
        const bigDividend = BigInt(this.toInteger(dividend)) * scale;
        const bigDivisor = BigInt(this.toInteger(divisor));
        const result = bigDividend / bigDivisor;
        return this.formatBigIntDecimal(result, precision);
    }
    const num1 = Number(dividend);
    const num2 = Number(divisor);
    const result = num1 / num2;
    return this.formatNumber(result, options?.precision);
}
  • 除法运算首先进行除数为 0 的校验,抛出错误。然后判断被除数和除数是否需要大数处理。若需要,根据指定精度或默认精度,将被除数乘以相应的 10 的幂次方转换为 BigInt 类型,与除数进行除法运算后,按照精度要求格式化结果。
  • 对于普通数字,直接进行除法运算,再根据精度格式化。

例如:

// 普通除法
NumberUtils.divide(0.3, 0.1); 
// 返回 3

// 大数除法
NumberUtils.divide('9007199254740991', '3');  
// 返回 '3002399751580330.333...'

// 指定精度
NumberUtils.divide(10, 3, { precision: 4 });  
// 返回 3.3333

5. 数字比较 - compare 方法

compare(n1: number | string, n2: number | string): -1 | 0 | 1 {
    if (this.needBigIntHandling(n1) || this.needBigIntHandling(n2)) {
        const big1 = BigInt(this.toInteger(n1));
        const big2 = BigInt(this.toInteger(n2));
        if (big1 < big2) return -1;
        if (big1 > big2) return 1;
        return 0;
    }
    const num1 = Number(n1);
    const num2 = Number(n2);
    if (num1 < num2) return -1;
    if (num1 > num2) return 1;
    return 0;
}
  • 该方法用于比较两个数字的大小关系。首先判断两个数字是否需要大数处理,若需要,将它们转换为 BigInt 类型进行比较,根据比较结果返回 -1(小于)、0(等于)或 1(大于)。
  • 对于普通数字,直接比较大小并返回相应结果。

例如:

NumberUtils.compare(5, 3);  // 返回 1

NumberUtils.compare('9007199254740991', '9007199254740990');  // 返回 1

四、使用示例

假设我们正在开发一个电商购物车页面,需要计算商品总价、折扣后的价格等:

import NumberUtils from './NumberUtils';

// 商品单价
const price1 = 19.99;
const price2 = 29.99;
// 商品数量
const quantity1 = 2;
const quantity2 = 3;

// 计算商品总价,可能涉及大数运算
const totalPrice = NumberUtils.add(
    NumberUtils.multiply(price1, quantity1),
    NumberUtils.multiply(price2, quantity2)
);
console.log('商品总价:', totalPrice);

// 假设全场 8 折
const discountRate = 0.8;
const discountedPrice = NumberUtils.multiply(totalPrice, discountRate);
console.log('折扣后价格:', discountedPrice);

// 比较商品数量,判断哪种商品购买更多
const compareResult = NumberUtils.compare(quantity1, quantity2);
if (compareResult === 1) {
    console.log('商品 1 购买数量更多');
} else if (compareResult === -1) {
    console.log('商品 2 购买数量更多');
} else {
    console.log('两种商品购买数量相同');
}

五、总结

NumberUtils 工具类为前端开发中的数字运算提供了强大而可靠的解决方案。无论是日常的小数值计算,还是涉及到大数字的复杂运算,它都能精准应对,并且通过精度控制和舍入模式设置,满足各种业务场景的特殊要求。掌握并合理运用这个工具类,将极大地提升前端项目中数字处理的准确性和稳定性,减少因数字运算错误导致的业务问题,为项目的顺利推进保驾护航。希望通过本文的详细介绍,大家能对 NumberUtils 有深入的理解,并灵活运用到实际开发中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值