问题:
前端在处理后端接口返回的列表数据进行表尾合计行统计,由于返回的金额不固定,有小数,整数,大数值等等,会出现累计计算数值精度丢失的问题,导致页面数据看起来是错的。
原因:
在JavaScript中,Number.MAX_SAFE_INTEGER 是一个常量,其值为 9007199254740991(2^53 - 1),代表JavaScript中可以安全表示的最大整数。这意味着任何大于 Number.MAX_SAFE_INTEGER 的整数在进行数学运算时可能会失去精度,因为JavaScript使用IEEE 754标准的64位浮点数表示数字,其中包括一个符号位、11位指数位和52位尾数位。
重写el-table 表尾合计行方法:
<template>
<div class="table-container">
<el-table
:data="tableData"
show-summary
:summary-method="getSummaries"
style="width: 100%">
<el-table-column prop="id" label="ID" width="120"></el-table-column>
<el-table-column prop="month" label="月份" width="120"></el-table-column>
<el-table-column prop="name" label="姓名"></el-table-column>
<el-table-column prop="amount1" label="整数金额" width="120"></el-table-column>
<el-table-column prop="amount2" label="小数金额" width="120"></el-table-column>
<el-table-column prop="amount3" label="混合金额" width="200"></el-table-column>
</el-table>
</div>
</template>
<script>
export default {
name: 'TableComponent',
data() {
return {
tableData: [
{
id: '12987122',
month: '2026-02',
name: '王小虎',
amount1: '234',
amount2: '3.2',
amount3: '0.11'
}, {
id: '12987123',
month: '2026-01',
name: '王小虎',
amount1: '165',
amount2: '4.43',
amount3: '0.88'
}, {
id: '12987124',
month: '2025-12',
name: '王小虎',
amount1: '324',
amount2: '1.9',
amount3: '2'
}, {
id: '12987125',
month: '2025-11',
name: '王小虎',
amount1: '621',
amount2: '2.2',
amount3: '123456789123456789'
}, {
id: '12987126',
month: '2025-10',
name: '王小虎',
amount1: '539',
amount2: '4.1',
amount3: '887654321987654321'
}
]
};
},
methods: {
getSummaries(param) {
const { columns, data } = param;
const sums = [];
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = '合计';
return;
}
const values = data.map(item => Number(item[column.property]));
if (column.property === 'amount3') {
// 对amount3字段进行高精度计算
const total = this.calculateHighPrecisionSum(data.map(item => item.amount3));
sums[index] = total;
} else if (!values.every(value => isNaN(value))) {
const precisions = [];
let notNumber = true;
values.forEach(value => {
if (!isNaN(value)) {
notNumber = false;
const decimal = value.toString().split('.')[1];
precisions.push(decimal ? decimal.length : 0);
}
});
if (!notNumber) {
const precision = Math.max(...precisions);
sums[index] = values.reduce((prev, curr) => {
const value = Number(curr);
if (!isNaN(value)) {
return parseFloat((prev + value).toFixed(Math.min(precision, 20)));
} else {
return prev;
}
}, 0);
} else {
sums[index] = 'N/A';
}
} else {
sums[index] = 'N/A';
}
});
return sums;
},
// 高精度计算方法,处理大数和小数的精确相加
calculateHighPrecisionSum(amounts) {
if (!amounts || amounts.length === 0) return '0';
// 使用数组来存储每一位数字,从低位到高位
let result = [];
let maxDecimalPlaces = 0;
// 先确定最大的小数位数
amounts.forEach(amount => {
const parts = amount.split('.');
if (parts.length > 1) {
maxDecimalPlaces = Math.max(maxDecimalPlaces, parts[1].length);
}
});
// 将所有数字转换为整数进行计算(乘以10^maxDecimalPlaces)
const integers = amounts.map(amount => {
const parts = amount.split('.');
let integerPart = parts[0];
let decimalPart = parts[1] || '';
// 补齐小数位
while (decimalPart.length < maxDecimalPlaces) {
decimalPart += '0';
}
return integerPart + decimalPart;
});
// 从右到左逐位相加
let carry = 0;
let maxLength = Math.max(...integers.map(num => num.length));
// 补齐位数
for (let i = 0; i < integers.length; i++) {
while (integers[i].length < maxLength) {
integers[i] = '0' + integers[i];
}
}
// 逐位相加
for (let i = maxLength - 1; i >= 0; i--) {
let sum = carry;
for (let j = 0; j < integers.length; j++) {
sum += parseInt(integers[j][i] || '0');
}
result.unshift(sum % 10);
carry = Math.floor(sum / 10);
}
// 处理最后的进位
while (carry > 0) {
result.unshift(carry % 10);
carry = Math.floor(carry / 10);
}
// 转换回小数形式
let resultStr = result.join('');
// 如果需要小数点,插入小数点
if (maxDecimalPlaces > 0) {
if (resultStr.length <= maxDecimalPlaces) {
// 如果结果长度小于小数位数,前面补0
while (resultStr.length < maxDecimalPlaces + 1) {
resultStr = '0' + resultStr;
}
}
const decimalIndex = resultStr.length - maxDecimalPlaces;
resultStr = resultStr.substring(0, decimalIndex) + '.' + resultStr.substring(decimalIndex);
// 去除末尾多余的0
resultStr = resultStr.replace(/\.?0+$/, '');
// 如果小数点在最后,去掉小数点
resultStr = resultStr.replace(/\.$/, '');
}
// 去除开头的0(但保留至少一个数字)
// 页面eslint校验报错可删除
resultStr = resultStr.replace(/+/, '') || '0';
return resultStr;
}
}
};
</script>
<style scoped>
.table-container {
padding: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
</style>
代码逻辑说明:
- 实现了Vue.js环境下的表格组件,包含表尾合计行功能
- 针对混合金额字段amount3,实现了高精度计算方法,支持小数、整数和大数的精确相加
- 使用字符串操作和逐位计算的方式,确保大数计算时不会丢失精度
- 合计计算从数值的最后面往前一步步计算,保证计算准确性
- 返回总金额的字符串格式,保持原始数据的精度特征
- 包含完整的样式设计,提供现代化的UI界面
记录一下,有好的方法可以评论区说说~