Kalan's Blog

本部落主要是關於前端、軟體開發以及我在日本的生活,也會寫一些對時事的觀察和雜感

目前主題 亮色

數值穩定與誤差

(0.1 + 0.2) == 0.3 這個看起來非常理所當然的等式,然而由於計算機對浮點數的儲存方式,在很多程式語言當中這個等式並不成立,也就是答案為 false

不過另外一個問題是,為什麼 (0.5 + 0.25) == 0.75 這個等式又會成立呢?

在做浮點數運算時多少都會出現誤差。接下來我們討論為什麼會有誤差,以及在計算上可以做哪些事情來避免。

浮點數在計算機的儲存方式

浮點數在電腦當中會以 bit 排列表示,由 IEEE754 所規範。其中單精度浮點數為 32bit;雙精度則為 64bit。 其中分成表示正負數的符號位、小數部、指數部。

  • 符號位(1bit):0 為正數;1 為負數
  • 小數部
  • 指數部
型別大小指數部小數部
單精度32bit8bit23bit
雙精度64bit11bit52bit

以單精度為例,由於指數部為 8bit,可以表示的範圍為 0 ~ 255,但因為需要表示負數,在 IEEE754 的規範當中需要加上一個偏移量,才是最後儲存在指數部的數字。以單精度來說,偏移量為 127。

舉例來說,-14.75 在浮點數的表示會是:

  • 符號位:負數,所以為 1
  • 指數部:先將 14.75 用二進位表示 1110.11,正規化後為 1.11011* 2^3,指數部為 3 加上 127 後為 130,二進位表示為 10000010
  • 小數部:11011,其餘的位數為 0

所以 -14.75 的浮點數表示會是:1 10000010 11011000000000000000000000022

從這邊我們也可以知道誤差從何而來,由於指數為負數後,得到的數字都是 0 < x < 1 之間,因此如果是無法透過 2^-x 表示的數字,就只能盡可能趨近而無法做到相同。開頭提到的 0.5 + 0.25,由於這些數字可以寫成 2^-1+ 2^-2,因此計算上不會出現誤差。

為什麼要這樣儲存?

使用科學記數表示(Scientific notation)可以幫助我們處理各種 scale 的數字,並且保持一定的精度。像是 0.000000123451234567890000,透過科學記數可以表示為 1.2345E-71.23456789E12。在計算機當中通常是使用浮點數格式儲存,跟科學記數類似,只是將十進位改成了二進位。

實數有無限多個,然而電腦的儲存空間有限,因此不管如何儲存都一定會有誤差。我們能做的是在精度與可表達的數字範圍作 trade-off。

有效數字

有效數字是幫助我們判斷精度的指標。像是 0.0010.0135 這些數字,我們可以寫作 1×1031 \times 10^{-3}1.35×1021.35\times10^{-2}。這時 0.001 的有效數字為 1 位,1.35 的有效數字為 3 位。有效數字位數越多,精度越高。

在維基百科有個簡單的判斷規則:

  • 所有非零數字都是有效的
  • 非零數字間的零都是有效的
  • 前綴零始終無效
  • 對於需要小數點的數,後綴零(最後一個非零數字後的零)是有效的
  • 對於不需要小數點的數,後綴零可能有效也可能無效。需要根據額外的符號或者誤差訊息決定。

Cancellation of significant digits

兩個絕對值非常相近的浮點數,在做減法運算時,因為大部分的數字相同,會剩下很多 0,導致有效數字減少,這個現象就叫做 cancellation of significant digits。

舉例來說:(1.234567890 - 1.234567889),雖然結果為 0.000000001,但在精度不足的情況下有可能變為 0

這在數值計算當中是需要特別注意的事:舉例來說倍角與半角公式

sin2(θ2)=1cosθ2\sin^2(\frac{\theta}{2}) = \frac{1-\cos\theta}{2}

當角度比較低的時候,由於 cos 相當接近 1,因此與 1 相減很容易出現有效數字遺失的問題。舉例來說:角度為 1 度時,使用倍角公式:(有效數字為 6 位數的情況下)

1cos1°2=10.9998472=0.0000765=7.65×105\frac{1-\cos1\degree}{2} = \frac{1 - 0.999847}{2} = 0.0000765 = 7.65 \times 10^{-5}

如果採用右邊的公式直接查表的話:

sin2(1°2)=0.008726532=0.00007615232=7.61523×105\sin^2(\frac{1\degree}{2}) = 0.00872653^{2} = 0.00007615232 = 7.61523 \times 10^{-5}

可以發現兩者的誤差相當明顯。在數值計算中要特別小心。要避免有效數字遺失有幾個方法:

  • 盡可能避免絕對值相近的兩個數做運算

  • 改用其他公式計算(例如上述的倍角公式)

    • 其實就是避免讓絕對值相近的兩個數做運算
  • 提高精度

結論

比較有經驗的工程師應該多少都知道浮點數運算會有誤差,也知道 0.1 + 0.2 != 0.3 的原因為何,這篇文章深入探討了小數的儲存方式、IEEE754、以及有效數字的遺失。

倍角公式應該是在高中三角函數時常常出現的題目,對我來說就是套套公式而已。

然而實際的應用都是透過電腦在算的,現實生活也不會到處都是 30 度 60 度這種好算的數字,老師也不會跟你說倍角公式會有有效數字的問題。

上一篇

興趣使然的研究之旅

下一篇

奇異值分解

如果覺得這篇文章對你有幫助的話,可以考慮到下面的連結請我喝一杯 ☕️ 可以讓我平凡的一天變得閃閃發光 ✨

Buy me a coffee

作者

Kalan 頭像照片,在淡水拍攝,淺藍背景

愷開 | Kalan

愷開。台灣人,在 2019 年到日本工作,目前定居在福岡。除了熟悉前端之外對 IoT、App 開發、後端、電子電路領域都有涉略。最近開始玩電吉他。 歡迎 Email 諮詢或合作,聊聊音樂也可以,希望能透過這個部落格和更多的人交流。