Jeff的隨手筆記

學習當一個前端工程師

0%

『菜鳥的踩坑筆記』- JavaScript浮點數

前言

雖然之前就知道 JavaScript 浮點數的問題,想不到居然在今天遇到了。這次的問題是因為專案中使用了 .toFixed() 這個 JavaScript 原生的方法,導致在進位時出現了一些問題。還好之前上課有認真,看到問題的第一時間就知道可能會是這樣,就往這方面去查,結果也如我預期的。

浮點數的問題

先來解釋什麼是 JavaScript 浮點數的問題:

1
2
console.log("輸出: ", 0.1 + 0.2);
// 輸出:0.30000000000000004

我相信你把這個拿給任何一個會加法的人都會跟你說答案是 0.3,但為什麼執行結果會不一樣呢?

這是因為我們平常在日常生活中使用的數字是叫做十進制,但電腦卻是使用二進制來存儲數字。當電腦要計算 0.1 + 0.2 時必須把這兩個數字轉換成二進制,但並不是所有的十進制數字都能被精確地表示為二進制。

.toFixed() 方法的局限性

.toFixed() 方法可以控制小數位數的顯示,但它並非處理 JavaScript 浮點數問題的完美解決方案。

基本用法:

1
2
3
let num = 0.1 + 0.2;
console.log(num); // 輸出:0.30000000000000004
console.log(num.toFixed(2)); // 輸出:"0.30"

看起來很完美,但實際上還是有問題:

1
console.log((1.005).toFixed(2));  // 輸出:"1.00"而不是預期的"1.01"

補充:.toFixed() 返回的是字符串,不是數字:

1
2
3
let result = (0.1 + 0.2).toFixed(2);
console.log(typeof result); // 輸出:"string"
console.log(result + 1); // 輸出:"0.301"而不是 1.3

因此,如果要進行進一步的數值運算,必須將結果轉回數字。

解決方法

之前我最常用的方式就是使用 Math.round() 這個方法:

1
2
3
4
5
6
7
function roundToTwo(num) {
return Math.round(num * 100) / 100;
}

console.log(roundToTwo(1.23456)); // 輸出: 1.23
console.log(roundToTwo(0.1 + 0.2)); // 輸出: 0.3

簡單介紹 Math.round() 這個方法:它是將數字四捨五入到最接近的整數。

  • 如果小數部分小於 0.5,向下取整。
  • 如果小數部分大於或等於 0.5,向上取整。
    這就很像我們直覺的四捨五入。

但我們總是要進步的,不能永遠吃老本。以下是一些其他解決方案:

Number.prototype.toPrecision()

toPrecision() 方法返回字串,可以搭配 parseFloat() 方法來轉換成數字:

1
2
3
4
5
6
7
8
let num = 123.456;
console.log(num.toPrecision()); // 輸出:"123.456"
console.log(num.toPrecision(4)); // 輸出:"123.5"

let smallNum = 0.000123;
console.log(smallNum.toPrecision(2)); // 輸出:"0.00012"
console.log(smallNum.toPrecision(5)); // 輸出:"0.00012300"

適用於需要控制整體精度的場景,但注意它同樣返回字串。

Intl.NumberFormat

Intl.NumberFormat 是 JavaScript 國際化 API (Internationalization API) 的一部分,用於根據特定語言和地區的規則格式化數字:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 基本語法
new Intl.NumberFormat([locales[, options]])

// 基本用法
const formatter = new Intl.NumberFormat('zh-TW');

// 設定小數位數
const priceFormatter = new Intl.NumberFormat('zh-TW', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});

// 貨幣格式
const currencyFormatter = new Intl.NumberFormat('zh-TW', {
style: 'currency',
currency: 'TWD'
});

options 常用的選項:

  • style:設置數字的顯示樣式(可選值:’decimal’ | ‘currency’ | ‘percent’ | ‘unit’)
  • minimumFractionDigits:最少要顯示的小數位數
  • maximumFractionDigits:最多能顯示的小數位數
  • useGrouping:是否使用千分位分隔符

但需要注意,它僅適合顯示,不適合精確計算。

總結

在處理 JavaScript 浮點數時,我們可以根據不同的場景選擇適合的解決方案:

  1. 一般場景:使用 Math.round() 配合乘除法
  2. 需要格式化顯示:使用 Intl.NumberFormat
  3. 特定精度需求:使用 toPrecision() 方法
  4. 需要精確計算:考慮使用專業的數學函式庫

最重要的是要理解 JavaScript 中浮點數的特性,在開發時預先考慮可能遇到的精度問題,選擇合適的解決方案。特別是在處理金額等敏感數據時,更要注意精度問題,確保計算結果的準確性。