前端必备:深入剖析 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
,将数字转换为整数相乘后再除以10
的m
次方,还原小数位,最后根据精度要求格式化结果。
例如:
// 普通乘法
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
有深入的理解,并灵活运用到实际开发中。