JavaScript:数值数据精度异常问题排查与解决
问题描述
我正在开发一个基于JavaScript、CSS和HTML的Web应用,涉及动态表格创建与数据同步功能。用户输入的值会显示在表格中并进行乘法等计算,但本该得到如1023.60的结果,却出现1023.5999999999999的精度异常。我尝试过使用.toFixed(2)方法以及转为字符串后截取小数点后两位的方式,但问题仍未解决。以下是我的JavaScript代码:
const purchasedStocks = []; let val = 0; document.addEventListener("DOMContentLoaded", function() { const sessionCells = JSON.parse(sessionStorage.getItem('sessionCells')) || []; const table = document.getElementById('portTable'); sessionCells.forEach(function(cell) { createCells(cell.nameSMCI, parseFloat(cell.valueSMCI).toFixed(2)); const hasSMCI = sessionCells.some(function(cell) { return cell.nameSMCI === "SMCI"; }); if (hasSMCI) { const matchingCell = sessionCells.find(cell => cell.nameSMCI === "SMCI"); purchasedStocks.push([ matchingCell.nameSMCI, matchingCell.valueSMCI, matchingCell.stockSMCI, matchingCell.priceSMCI, matchingCell.sumSMCI, matchingCell.countSMCI ]); } }); document.addEventListener("DOMContentLoaded", function() { }); document.getElementById("overlaySubmitSMCI").addEventListener("click", function () { event.preventDefault(); cFunction(); const overlay = document.getElementById('overlay'); overlay.style.display = 'none'; const price = data.SMCI.number; const inputField = document.getElementById("stockInput"); let stock = 0; let value = 0; if (val == 0) { stock = (inputField.valueAsNumber / price).toFixed(2); value = inputField.valueAsNumber.toFixed(2); } else { stock = inputField.valueAsNumber.toFixed(2); value = (inputField.valueAsNumber * price).toFixed(2); } if(hasEntry(purchasedStocks, "SMCI")== -1) { purchasedStocks.push(["SMCI", value, stock, price, price, 1]); createCells("SMCI", value); sessionCells.push({ nameSMCI: "SMCI", valueSMCI: value, stockSMCI: stock, priceSMCI: price, sumSmci: price, countSMCI: 1 }); sessionStorage.setItem('sessionCells', JSON.stringify(sessionCells)); } else{ $i=hasEntry(purchasedStocks, "SMCI"); purchasedStocks[$i][1]=parseFloat(purchasedStocks[$i][1])+parseFloat(value); purchasedStocks[$i][3]=addValueAndCalculateAverage(parseFloat(price), parseFloat(purchasedStocks[$i][4]), parseFloat(purchasedStocks[$i][5])); purchasedStocks[$i][2]=parseFloat(purchasedStocks[$i][2])+parseFloat(stock); purchasedStocks[$i][4]=parseFloat(price)+parseFloat(purchasedStocks[$i][4]); purchasedStocks[$i][5]=parseFloat(purchasedStocks[$i][5])+1; const cell = document.getElementById('cell2SMCI'); cell.textContent = parseFloat(purchasedStocks[$i][1]); const existingEntry = sessionCells.find(entry => entry.nameSMCI === "SMCI"); existingEntry.valueSMCI = purchasedStocks[$i][1]; existingEntry.stockSMCI = purchasedStocks[$i][2]; existingEntry.priceSMCI = purchasedStocks[$i][3]; existingEntry.sumSMCI = purchasedStocks[$i][4]; existingEntry.countSMCI = purchasedStocks[$i][5]; sessionStorage.setItem('sessionCells', JSON.stringify(sessionCells)); } }); }); function cFunction() { Swal.fire({ title: '<span style="color: white;">Credits!<span>', html: '<div style=line-height: 25px;"><span style="color: white;">Author: Jaša Gregorič<br>Date: November 2023<br>Mentor: Boštjan Vouk</span></div>', icon: 'info', confirmButtonText: 'OK', iconColor: 'white', background: '#1d1d1d', heightAuto: false }); } function createCells($n, $v){ const table = document.getElementById('portTable'); const newRow = document.createElement('tr'); const newCell1 = document.createElement('td'); newCell1.textContent = $n; newCell1.setAttribute('id', ("cell1"+$n)); const newCell2 = document.createElement('td'); newCell2.textContent = numberToString(parseFloat($v).toFixed(2)); newCell2.setAttribute('id', ("cell2"+$n)); newRow.appendChild(newCell1); newRow.appendChild(newCell2); table.appendChild(newRow); localStorage.setItem('myTableHTML', table.innerHTML); } function addValueAndCalculateAverage(newValue, sum, count) { count++; sum += newValue; const newAverage = sum / count; return newAverage; } function numberToString(inputNumber) { const numberString = inputNumber.toString(); const parts = numberString.split('.'); return parts[0] + '.' + parts[1].substring(0, 2); }
解决方案
问题根源
JavaScript用二进制浮点数存储数值,部分十进制小数(比如0.1、0.6)无法被精确表示,计算时会累积微小误差。你的代码里还有几个放大误差的问题:
- 计算过程中过早用
toFixed(2)转成字符串,后续又转成浮点数计算,相当于反复截断再恢复,反而引入更多误差; numberToString函数没处理无小数位的情况,容易报错;- 更新表格内容时直接输出浮点数,没做格式化处理。
具体修复步骤
计算全程保留数值类型,只在展示/存储时格式化
不要在计算中间把数值转成字符串,比如stock和value的计算先保留原始数值,最后展示或存储时再格式化到两位小数。用整数运算避免浮点误差
把所有涉及金额/精度的计算放大100倍转成整数,计算完成后再缩小100倍,彻底避免浮点精度问题。修正关键代码逻辑
- 重写
addValueAndCalculateAverage函数,加入精度处理; - 更新表格单元格内容时强制格式化到两位小数;
- 优化
numberToString函数,处理边界情况; - 存储到
sessionStorage时,存储格式化后的字符串,避免读取时的精度问题。
- 重写
修改后的关键代码示例
1. 优化计算逻辑(点击事件中的stock/value计算)
if (val == 0) { // 保留数值类型,不转字符串 stock = inputField.valueAsNumber / price; value = inputField.valueAsNumber; } else { stock = inputField.valueAsNumber; value = inputField.valueAsNumber * price; }
2. 修复平均值计算函数
function addValueAndCalculateAverage(newValue, sum, count) { // 转成整数运算避免误差 const newSum = Math.round(sum * 100) + Math.round(newValue * 100); const newCount = count + 1; const newAverage = newSum / 100 / newCount; // 格式化到两位小数返回,或者保留数值在展示时格式化 return Math.round(newAverage * 100) / 100; }
3. 更新表格内容时格式化
// 替换原来的cell.textContent赋值 const cell = document.getElementById('cell2SMCI'); cell.textContent = (parseFloat(purchasedStocks[$i][1])).toFixed(2);
4. 优化numberToString函数
function numberToString(inputNumber) { // 先格式化到两位小数,再转字符串 return inputNumber.toFixed(2); }
5. 存储到sessionStorage时格式化
// 新增条目时 sessionCells.push({ nameSMCI: "SMCI", valueSMCI: value.toFixed(2), stockSMCI: stock.toFixed(2), priceSMCI: price.toFixed(2), sumSmci: price.toFixed(2), countSMCI: 1 }); // 更新条目时 existingEntry.valueSMCI = purchasedStocks[$i][1].toFixed(2); existingEntry.stockSMCI = purchasedStocks[$i][2].toFixed(2); existingEntry.priceSMCI = purchasedStocks[$i][3].toFixed(2); existingEntry.sumSMCI = purchasedStocks[$i][4].toFixed(2); existingEntry.countSMCI = purchasedStocks[$i][5].toString();
核心原则
- 计算过程中尽量用原始数值(或整数运算),减少类型转换;
- 仅在最终展示和持久化存储时,将数值格式化为两位小数的字符串;
- 避免反复对浮点数进行截断和恢复操作,防止误差累积。
内容的提问来源于stack exchange,提问作者jasagregoric
相关产品推荐
相关产品推荐

