You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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函数没处理无小数位的情况,容易报错;
  • 更新表格内容时直接输出浮点数,没做格式化处理。

具体修复步骤

  1. 计算全程保留数值类型,只在展示/存储时格式化
    不要在计算中间把数值转成字符串,比如stock和value的计算先保留原始数值,最后展示或存储时再格式化到两位小数。

  2. 用整数运算避免浮点误差
    把所有涉及金额/精度的计算放大100倍转成整数,计算完成后再缩小100倍,彻底避免浮点精度问题。

  3. 修正关键代码逻辑

    • 重写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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 11:18:13