处理数字是前端开发中常见的任务之一。通过使用格式化、转换和校验等方法,我们可以对数字进行各种操作,满足不同的业务需求。在实际开发中,根据具体需求选择适合的方法,并注意处理边界情况和错误处理,以确保数字的正确性和可靠性。
在前端中,最基本的处理数字的方式主要有以下几种:
- 将数字转换为字符串,可以使用toString()方法将数字转换为字符串,然后对字符串进行操作。
- 使用parseInt()方法将字符串转换为整数,或者使用parseFloat()方法将字符串转换为浮点数。
- 使用正则表达式进行数字匹配,可以使用正则表达式匹配字符串中的数字,然后对匹配到的数字进行操作。
- 使用Number对象的方法进行数字操作,Number对象提供了一些常用的数字方法,如toFixed()、toPrecision()等,可以对数字进行格式化、舍入等操作。
- 使用JavaScript的Math对象进行数学运算,Math对象提供了一些常用的数学方法,如abs()、round()、max()、min()等,可以进行数值计算和数值比较等操作。
除此之外,因为处理数字的方式比较灵活多样,需要根据具体的场景选择合适的方法。当涉及到数字处理时,有许多常见的场景和需求需要考虑。以下是一些常见的数字处理方案,可以帮助您更好地处理数字数据
数字展示和计算
数字转大写(财务金额版)
用于将数字金额转换为中文大写金额的功能。它接受一个数字作为输入参数,并返回对应的中文大写金额表示。通过调用该方法,可以方便地将数字金额转换为中文大写金额,满足财务报表、合同等场景对于金额的要求。例如,调用 convertCurrency(1234.56)
转换为人民币 “壹仟贰佰叁拾肆元伍角陆分”。
const convertCurrency = (money) => {
const cnNums = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']; // 汉字的数字
const cnIntRadice = ['', '拾', '佰', '仟']; // 基本单位
const cnIntUnits = ['', '万', '亿', '兆']; // 对应整数部分扩展单位
const cnDecUnits = ['角', '分', '毫', '厘']; // 对应小数部分单位
const cnInteger = '整'; // 整数金额时后面跟的字符
const cnIntLast = '元'; // 整型完以后的单位
const maxNum = 9999999999999.99; // 最大处理的数字
let integerNum; // 金额整数部分
let decimalNum; // 金额小数部分
let chineseStr = ''; // 输出的中文金额字符串
let parts; // 分离金额后用的数组,预定义
if (!money) {
return;
}
money = parseFloat(money);
if (money >= maxNum) {
return;
} // 超出最大处理数字
if (money === 0) {
chineseStr = cnNums[0] + cnIntLast + cnInteger; // 零 + 元 + 整
return chineseStr;
}
money = money.toString(); // 转换为字符串
if (money.indexOf('.') === -1) {
// 只有整数的处理
integerNum = money; // 金额整数部分
decimalNum = ''; // 金额小数部分
} else {
// 有小数的处理
parts = money.split('.');
integerNum = parts[0]; // 整数部分 // 金额整数部分
decimalNum = parts[1].substr(0, 4); // 最多处理4位小数 // 金额小数部分
}
// 获取整型部分转换
if (parseInt(integerNum, 10) > 0) {
let zeroCount = 0;
const IntLen = integerNum.length; // 金额整数部分
for (let i = 0; i < IntLen; i++) {
const n = integerNum.substr(i, 1); // 金额整数部分
const p = IntLen - i - 1;
const q = p / 4;
const m = p % 4;
if (n === '0') {
zeroCount++; // 位数直接加1
} else {
if (zeroCount > 0) {
chineseStr += cnNums[0]; // 位数补零
}
zeroCount = 0; // 归零
chineseStr += cnNums[Number(n)] + cnIntRadice[m];
}
if (m === 0 && zeroCount < 4) {
chineseStr += cnIntUnits[q];
}
}
chineseStr += cnIntLast; // 处理完整数部分,补上元
}
// 小数部分
if (decimalNum !== '') {
const decLen = decimalNum.length; // 金额小数部分
for (let i = 0; i < decLen; i++) {
const n = decimalNum.substr(i, 1);
if (n !== '0') {
chineseStr += cnNums[Number(n)] + cnDecUnits[i];
}
}
}
if (chineseStr === '') {
// 最后处理一遍 整数部分
chineseStr += cnNums[0] + cnIntLast + cnInteger; // 零 + 元 + 整
} else if (decimalNum === '') {
// 金额小数部分
// 最后处理一遍 小数部分
chineseStr += cnInteger; // 整
}
return chineseStr;
};
数字转换为中文数字
这个方法是将数字转换为中文数字的表示形式。它接受一个数字作为参数,然后将其转换为对应的中文数字表示。调用 numberTranToCN(12345)
将返回 “一万二千三百四十五”。
export const numberTranToCN = (num: number) => {
const numChar = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
const numUnit = ['', '十', '百', '千'];
const arr = String(num).split('').reverse(); // 先转数字,再使用 split('') 方法将其拆分为单个字符,并通过 reverse() 方法反转顺序,以便从低位到高位进行处理
let resStr = '';
// 循环遍历每个字符,并根据其在数字中的位置,结合 numChar 和 numUnit 数组,生成对应的中文数字字符和单位。
for (let i = 0; i < arr.length; i++) {
const char = arr[i] === '0' ? numChar[0] : numChar[Number(arr[i])] + numUnit[i];
resStr = char + resStr;
}
// 对生成的中文数字字符串进行一些替换操作,以去除多余的零和调整一些特殊情况,例如将 "一十" 替换为 "十"。
const s = resStr
.replace(/零+/g, '零')
.replace(/零+$/, '')
.replace(/一十(\w?)/, '十$1');
return s;
};
价格千位符处理
用于对价格进行千位符处理的函数。例如,调用 priceInt(1234567.89)
将返回 “1,234,567.89”。
// 价格千位符处理
export const priceInt = (val) => {
if (val) {
val = val.toString();
const arr = val.split('.');
const price = arr[0].toString().replace(/\B(?=(\d{3})+$)/g, ',');
return price + (arr[1] ? `.${arr[1]}` : '');
}
return val;
};
重写toFixed的方法;toFixed兼容方法
大家都知道toFixed
方法可以用来保留两位小数,但是在一些特定的场景下toFixed
无法给出我们想要的值。比如四舍五入时,toFixed(2) 对于 0.615 可能返回 0.61 而不是预期的 0.62。
/**
* 重写toFixed的方法;toFixed兼容方法
* @param number // 原数字
* @param digit // 保留的位数,默认是2
* @returns
*/
export const toFixedDigit = function (number: number, digit = 2) {
// 如果小数位数超出了范围(大于20或小于0或不是正整数),则会抛出一个错误。
if (digit > 20 || digit < 0 || !Number.isInteger(digit)) {
throw new RangeError('toFixed() digits argument must be between 0 and 20');
}
//如果数字是NaN或大于等于10的21次方,则直接返回数字的字符串表示。
// const number: any = this;
if (isNaN(number) || number >= Math.pow(10, 21)) {
return number.toString();
}
// 如果小数位数未指定或为0,则返回四舍五入后的整数。
if (typeof digit == 'undefined' || digit == 0) {
return Math.round(number).toString();
}
let result: string | number = number.toString(); // 原数值转字符串
const arr = result.split('.'); // 从小数点分割
// 仅有整数的情况,补0
if (arr.length < 2) {
result += '.';
for (let i = 0; i < digit; i += 1) {
result += '0';
}
return result;
}
const integer = arr[0]; // 整数部分
const decimal = arr[1]; // 小数部分
//如果小数位数与原始数字的小数位数相同,则直接返回原始结果。
if (decimal.length == digit) {
return result;
}
//如果小数位数少于原始数字的小数位数,则在结果后面添加零以达到指定的小数位数。
if (decimal.length < digit) {
for (let i = 0; i < digit - decimal.length; i += 1) {
result += '0';
}
return result;
}
//如果小数位数多于原始数字的小数位数,则截取原始数字的小数部分并返回。
result = integer + '.' + decimal.substring(0, digit);
const last = decimal.substring(digit, digit + 1);
// 四舍五入,转换为整数再处理,避免浮点数精度的损失
if (parseInt(last, 10) >= 5) {
//如果小数部分的下一位数字大于等于5,则进行四舍五入,将结果转换为整数,并再次调用toFixed函数以确保小数位数正确
const x = Math.pow(10, digit);
result = (Math.round(parseFloat(result) * x) + 1) / x;
result = result.toFixed(digit);
}
return result;
};
用于实现两个数字的精确加法运算
function accAdd (arg1, arg2) {
var r1, r2, m;
// 尝试将第一个参数 arg1 转换为字符串,然后通过分割字符串获取小数部分,并计算小数部分的长度。如果出现错误(例如 arg1 不是数字或没有小数部分),则将 r1 设置为 0。
try{
r1 = arg1.toString().split(".")[1].length
} catch (e) {
r1 = 0
}
try{
r2 = arg2.toString().split(".")[1].length
} catch (e) {
r2 = 0
}
// 确定倍数:计算两个参数中小数位数较大者对应的倍数,即 10 的 Math.max(r1, r2) 次方。这个倍数用于将两个数字都扩大到整数,以便进行精确的加法运算。
m = Math.pow(10, Math.max(r1,r2))
// 进行加法运算并返回结果:将两个参数分别乘以 m,转换为整数后进行加法运算,然后再除以 m,得到精确加法后的结果。
return (parseInt(arg1*m, 10) + parseInt(arg2*m, 10)) / m
}
乘除计算,保留两位小数(mathjs)。
前端在处理计算时常会遇到精度丢失的问题,下面的方法解决了精度丢失,还要保留两位小数的数字,例如:0.75 * 421.9 = 316.425 ,计算机的值为316.424999…,后端需要的值是316.43(也就是316.425的四舍五入)。这个方法用到了mathjs
的divide
, bignumber
, multiply
。
import { divide, bignumber, multiply } from 'mathjs';
/**
* 乘除计算,保留两位小数
* @param record:any // 数据来源
* @param field: 'multiply' || 'divide' // 乘 | 除
* @returns
*/
export const feePrice = (record: any, field: string) => {
if (field === 'multiply') {
// record.feePrice = parseFloat(parseFloat((record.unitPrice * record.rentArea).toFixed(3)).toFixed(2));// 不满足1.5*218.21返回327.32
// 解决精度丢失,还要保留两位小数的数字,例如:0.75 * 421.9 = 316.425 ,计算机的值为316.424999..,后端需要的值是316.43(也就是316.425的四舍五入)
try {
record.price = toFixedDigit(Number(multiply(bignumber(record.unitPrice), bignumber(record.rentArea))), 2);
// mathjs中的计算方法['multiply', 'divide']中的值都需要是一个'bignumber'。并且计算方法返回的不是一个数字,需要对返回结果转数字处理。
// toFixedDigit 是上面的"重写toFixed的方法",因为计算结果不一定是满足条件的两位小数
} catch (error) {
record.rice = 0;
}
} else if (field === 'divide') {
// record.unitPrice = record.feePrice / record.rentArea || 0;
// record.unitPrice = parseFloat((((record.feePrice * 100) / (record.rentArea * 100)) / 10000).toFixed(2)) || 0;
try {
record.price = toFixedDigit(Number(divide(bignumber(record.feePrice), bignumber(record.rentArea))), 2);
} catch (err) {
record.price = 0;
}
}
return record;
};
数字验证:
根据传入的验证规则对数字进行校验,并返回一个 Promise 对象。如果校验通过,Promise 对象将会被解析为一个空值;如果校验不通过,Promise 对象将会被拒绝,并返回一个包含错误信息的 Error 对象。
/**
* 校验数字的验证规则函数
* @param _:any // 包含验证规则的对象
* @param value: number | string // 待验证的值
* @param allowZero: boolean // 是否允许值为零
* @returns
**/
const checkNumberBase = (_, value, allowZero = true) => {
const reg = /^\d*(\.\d{1,2})?$/;
const { requiredSelf, messageSelf, required, min, max, equal } = _;
if (typeof value !== 'number') {
value = Number(value);
}
if (requiredSelf && !value) {
return Promise.reject(new Error(messageSelf || '请输入'));
}
if (!value) {
if (required) {
return Promise.reject(new Error('请输入'));
}
return Promise.resolve();
}
if (Number.isNaN(value)) {
return Promise.reject(new Error('请输入数字值'));
}
if (max && value > max) {
return Promise.reject(new Error(`最大值为${max}`));
}
if (max && equal && value >= max) {
return Promise.reject(new Error(`值需小于${max}`));
}
if (min && value < min) {
return Promise.reject(new Error(`最小值为${min}`));
}
if (!allowZero && value === 0) {
return Promise.reject(new Error('不能为0'));
}
if (value < 0) {
return Promise.reject(new Error('不能为负数'));
}
if (!reg.test(value)) {
return Promise.reject(new Error('最多为两位小数'));
}
return Promise.resolve();
};