问题:
前端在处理后端接口返回的列表数据进行表尾合计行统计,由于返回的金额不固定,有小数,整数,大数值等等,会出现累计计算数值精度丢失的问题,导致页面数据看起来是错的。
原因:
在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界面
记录一下,有好的方法可以评论区说说~