JavaScript浮點數精度Math.sumPreciseBigDecimalWASM前端2026

前端大數運算與浮點數精度:2026 年的新發展與實戰指南

整理 2026 年前端數值計算的新發展:Math.sumPrecise Stage 4、BigDecimal Stage 1、WASM 高精度方案,以及電商、加密、財務三大場景的完整實戰指南。

· 6 分鐘閱讀

如果你的應用涉及任何形式的數值計算——電商定價、加密貨幣錢包、財務儀表板——你一定踩過浮點數的坑。0.1 + 0.2 !== 0.3Number.MAX_SAFE_INTEGER 以上的 ID 被截斷——這些問題不是 bug,而是 JavaScript 採用的 IEEE 754 浮點數標準的根本限制。2026 年,這個領域有幾個重要的新發展:Math.sumPrecise 已經到了 Stage 4(Firefox 已實作)、BigDecimal 提案進入了 Stage 1、 WASM 高精度方案越來越成熟。這篇文章是「前端數字地雷」的進階版,專注於 2026 年的新發展與實戰場景。

關於浮點數基礎、BigInt 基本用法、decimal.js 入門,建議先參考 前端數字地雷:JavaScript 浮點數精度與大數處理的正確解法


2026 年數值計算的整體圖景

在 2026 年,前端數值計算的生態已經明顯分层:

層次場景解決方案
原生基礎簡單加總、大整數 IDMath.sumPrecise(新)、BigInt
庫支撐電商、金融、科學計算decimal.js、bignumber.js
WASM 高效能大量資料、高精度需求MPFR.js
未來期待原生高精度小數BigDecimal(Stage 1)

選擇哪個層次的方案,取決於你的具體場景。


Math.sumPrecise:Stage 4 的精確加總

什麼是 Math.sumPrecise

Math.sumPrecise 是 TC39 提出的新提案,專門解決多個浮點數相加時的累積誤差問題。

// 過去的問題
0.1 + 0.2 + 0.3 + 0.4 + 0.5; // 0.1 + 0.2 = 0.30000000000000004

// Math.sumPrecise 的解決
Math.sumPrecise(0.1, 0.2, 0.3, 0.4, 0.5); // 1.5(精確)

// 也支援陣列
Math.sumPrecise([0.1, 0.2, 0.3, 0.4, 0.5]); // 1.5

當前支援狀態

瀏覽器支援
Firefox✅ 已實作
Chrome🔜 即將支援
Safari🔜 即將支援
Node.js✅ v22+
// Fallback 模式
function safeSum(...numbers) {
  if (typeof Math.sumPrecise === 'function') {
    return Math.sumPrecise(...numbers);
  }
  // 退回到整數法
  const sum = numbers.reduce((acc, n) => {
    const [int, dec] = n.toString().split('.');
    const decimals = dec ? dec.length : 0;
    const multiplier = 10 ** decimals;
    return acc + n * multiplier;
  }, 0);
  return sum / 10000; // 需要更好的方式
}

適用場景與限制

// ✅ 適用:多個小數加總
const subtotal = Math.sumPrecise(
  item1.price,
  item2.price,
  item3.price,
  discount
);

// ❌ 不適用:需要減法、除法、乘法
const total = subtotal * 1.05; // 仍然會有浮點誤差
const withTax = total / 1.05;  // 除法誤差仍存在

Math.sumPrecise 只解決「加總」的精度問題,不是萬靈丹。


三大實戰場景的完整方案

場景一:電商定價

問題:折扣、稅金、運費的組合計算,每一步都可能產生浮點誤差。

import Decimal from 'decimal.js';

class PricingCalculator {
  constructor(currency = 'TWD') {
    this.currency = currency;
  }

  // 計算折扣後價格
  applyDiscount(price, discountPercent) {
    return new Decimal(price)
      .times(discountPercent)
      .dividedBy(100)
      .toFixed(2);
  }

  // 計算含稅價格
  addTax(price, taxRate = 5) {
    return new Decimal(price)
      .times(taxRate)
      .dividedBy(100)
      .plus(price)
      .toFixed(2);
  }

  // 計算訂單總價(避免浮點誤差)
  calculateOrderTotal(items, discountPercent, taxRate = 5) {
    const subtotal = items.reduce(
      (sum, item) => sum.plus(new Decimal(item.price).times(item.quantity)),
      new Decimal(0)
    );

    const discountAmount = subtotal
      .times(discountPercent)
      .dividedBy(100);

    const afterDiscount = subtotal.minus(discountAmount);
    const tax = afterDiscount.times(taxRate).dividedBy(100);
    const total = afterDiscount.plus(tax);

    return {
      subtotal: subtotal.toFixed(2),
      discount: discountAmount.toFixed(2),
      tax: tax.toFixed(2),
      total: total.toFixed(2),
    };
  }
}

// 使用範例
const calculator = new PricingCalculator('TWD');
const order = calculator.calculateOrderTotal([
  { price: '299.00', quantity: 3 },
  { price: '599.00', quantity: 1 },
  { price: '149.00', quantity: 2 },
], 10, 5); // 10% 折扣, 5% 稅

console.log(order);
// { subtotal: '1793.00', discount: '179.30', tax: '80.69', total: '1694.39' }

關鍵:所有的價格都作為字串傳入 Decimal.js,避免 JavaScript 浮點數在傳入時就失去精度。

場景二:加密貨幣錢包

問題:加密貨幣的數量可能非常大(小數位極多),且涉及除法(分割代幣)。

import { BigNumber, ethers } from 'ethers';

// EVM 鏈上的代幣金額通常以最小單位(wei, satoshi)存儲
const formatTokenAmount = (rawAmount, decimals = 18) => {
  const bn = BigNumber.from(rawAmount);
  const divisor = BigNumber.from(10).pow(decimals);
  const integerPart = bn.div(divisor);
  const fractionalPart = bn.mod(divisor);

  // 格式化為人類可讀的數字
  const fractionalStr = fractionalPart.toString().padStart(decimals, '0');
  const formattedFraction = fractionalStr.slice(0, 6); // 只顯示 6 位

  return `${integerPart.toString()}.${formattedFraction}`;
};

// 轉帳時,確認餘額足夠
const transfer = async (from, to, amount) => {
  const decimals = 18;
  const parsedAmount = ethers.utils.parseUnits(amount, decimals);

  const balance = await provider.getBalance(from);
  const gasEstimate = await provider.estimateGas({ from, to, value: parsedAmount });

  if (balance.lt(parsedAmount.add(gasEstimate))) {
    throw new Error('餘額不足');
  }

  return signer.sendTransaction({ to, value: parsedAmount });
};

關鍵:區塊鏈上的代幣全部以整數存儲(最小單位),只有在展示時才轉換為小數。

場景三:財務儀表板

問題:財務數據需要嚴格的精度控制,且經常需要對齊顯示(小數位數一致)。

import Decimal from 'decimal.js';
import 'intl'; // Intl.NumberFormat

class FinancialDashboard {
  constructor(locale = 'zh-TW', currency = 'USD') {
    this.locale = locale;
    this.currency = currency;
    this.formatter = new Intl.NumberFormat(locale, {
      style: 'currency',
      currency: currency,
      minimumFractionDigits: 2,
      maximumFractionDigits: 2,
    });
  }

  // 精確計算投資回報率
  calculateROI(initialInvestment, finalValue) {
    const initial = new Decimal(initialInvestment);
    const final = new Decimal(finalValue);

    const roi = final
      .minus(initial)
      .dividedBy(initial)
      .times(100);

    return {
      percentage: roi.toFixed(2) + '%',
      absolute: this.formatCurrency(final.minus(initial).toFixed(2)),
    };
  }

  // 計算內部報酬率(IRR)
  calculateIRR(cashFlows, guess = 0.1) {
    const flows = cashFlows.map(cf => new Decimal(cf));
    let rate = new Decimal(guess);

    // Newton-Raphson 迭代
    for (let i = 0; i < 100; i++) {
      let npv = new Decimal(0);
      let derivative = new Decimal(0);

      flows.forEach((cf, t) => {
        const factor = rate.plus(1).pow(t);
        npv = npv.plus(cf.dividedBy(factor));
        derivative = derivative.minus(
          cf.times(t).dividedBy(factor.times(rate.plus(1)))
        );
      });

      if (npv.abs().lt(0.01)) break; // 收斂

      rate = rate.minus(npv.dividedBy(derivative));
    }

    return rate.times(100).toFixed(4) + '%';
  }

  formatCurrency(amount) {
    return this.formatter.format(amount);
  }
}

2026 Library 完整比較

Library大小(min)小數支援BigInt 支援維護狀態適合場景
decimal.js~50KB完整活躍金融、電商
bignumber.js~25KB完整活躍效能敏感場景
big.js~5KB完整輕量簡單計算
Fraction.js~20KB分數少量科學計算
currency.js~2KB專用少量僅貨幣計算

選擇決策樹

你的場景需要小數嗎?
├── 否 → BigInt(原生,無依賴)
└── 是 → 你的場景是金融/電商嗎?
    ├── 是 → decimal.js
    └── 否 → 需要 BigInt 互通嗎?
        ├── 是 → bignumber.js
        └── 否 → big.js(最小)

WASM 高精度計算:MPFR.js

對於高效能、高精度需求的場景(如科學計算、圖形處理),純 JavaScript 的 library 可能不夠。

// MPFR.js - Multiple Precision Floating-Point Reliability
import { MPFR } from 'mpfrjs';

const a = new MPFR('0.1');
const b = new MPFR('0.2');
const sum = a.add(b);

console.log(sum.toString()); // '0.3'(精確)

// 效能對比
console.time('decimal.js');
for (let i = 0; i < 10000; i++) {
  new Decimal('0.1').plus(new Decimal('0.2'));
}
console.timeEnd('decimal.js'); // ~50ms

console.time('MPFR.js');
for (let i = 0; i < 10000; i++) {
  new MPFR('0.1').add(new MPFR('0.2'));
}
console.timeEnd('MPFR.js'); // ~5ms(10x 更快)

取捨:MPFR.js 的效能是 decimal.js 的 10 倍,但:

  • Bundle 大小增加(~1MB WASM)
  • 初始載入時間更長
  • API 更複雜

常見錯誤速查表

錯誤寫法正確寫法原因
price * 100new Decimal(price).times(100)浮點乘法有誤差
0.1 + 0.2 === 0.3new Decimal(0.1).plus(0.2).equals(0.3)浮點相等性不可靠
parseFloat((a / b).toFixed(2))new Decimal(a).dividedBy(b).toFixed(2)toFixed 是字串方法
BigInt(0.1)new Decimal(0.1).toFixed(0)BigInt 不支援小數
金額存成 number金額存成 stringDecimal避免 JSON 序列化精度丟失

未來展望:BigDecimal Stage 1

JavaScript 官方終於要迎來原生的高精度小數類型了。

// 提案語法(Stage 1)
const price = BigDecimal('29.99');
const tax = price * BigDecimal('0.05');
const total = price + tax;

// 或使用靜態工廠
const total = BigDecimal.add(price, tax);

與現有方案的比較

維度BigDecimal(未來)decimal.js(現在)
Bundle 負擔0(原生)~50KB
API 成熟度待定成熟
小數精度完整完整
瀏覽器支援未來全部

時間線預測

  • 2026 年:Stage 2 或 Stage 3
  • 2027-2028 年:Stage 4,全面支援

在那之前,decimal.js 仍然是金融和電商場景的首選。


結語:選擇正確的工具

2026 年的前端數值計算,已經有了清晰的工具層次:

  1. 大整數 ID:直接用 BigInt(原生,無依賴)
  2. 簡單加總Math.sumPrecise(Stage 4,Firefox 已支援)
  3. 電商/金融decimal.js(成熟、功能完整)
  4. 高效能科學計算:MPFR.js(WASM)
  5. 僅展示格式化Intl.NumberFormat(原生)

沒有銀彈——根據你的場景選擇正確的工具,才是避免數值精度問題的關鍵。

關於浮點數的基礎知識和 BigInt 的基本用法,請參考 前端數字地雷:JavaScript 浮點數精度與大數處理的正確解法


本文是「2026 前端實戰」系列文章之一。