iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 10
1
自我挑戰組

Let's Eat GO ! 實務開發雜談by Golang系列 第 10

Day10 .[正確資料篇] 浮點數運算請decimal package協助

decimal package

今天不是特別要來教學decimal的套件怎麼使用,如果各位有找到不錯的處理浮點數套件或解法也非常好,根據各自的需求挑選合適的解決辦法。

呼籲的重點,是希望對於還沒深陷過浮點數處理問題的人,有些警覺,這種問題要能夠有個精準的解法實在太困難,因此求助外部套件,是最後的選擇方案。

為什麼說問題很困難,還不清楚的人可以稍微參考一下wiki,這是目前電腦是一定會遇到的議題,後面的原理太過數學和深究,所以筆者也無法一時用簡單的方式,就能將細節說明清楚。

要分享是曾經我以為的天真處理方式,能夠簡單地把浮點數問題處理好,愚蠢的作法經驗。

這是我寫的浮點數無條件捨去處理method

// Floor 無條件捨去小數第N位
func Floor(number float64, N int) (floorNumber float64) {
	pow := math.Pow10(N)
	floorNumber = math.Floor(Round(number*pow, 4)) / pow
	return
}

或者是這樣

base := 1000000000000
origin := "19.08"

originToFloat, _ := strconv.ParseFloat(origin, 64)

b := originToFloat * float64(base)
c := math.Trunc(b*1e2) * 1e-2 / float64(base)

若是四捨五入的處理,問題比較不容易遇到或發現,因為發生浮點數誤差的時候,容易因為進位或捨去剛好做掉誤差值。

如發生誤差,12 變成了 11.999999999999999 之類的樣子,你是用四捨五入的處理,11.999999999999999 基本上會做進位,變成12,最後的結果剛好沒有問題,符合需要。

問題在於若要做無條件捨去的處理,浮點數精準度的問題就會很容易暴露出來。

舉幾個數字,若要捨去小數後兩位,理想上應該是這樣
10 --> 10
1.23 --> 1.23
2.456 --> 2.45
66.789 --> 66.78

但實際上經過我的程式處理,1.23變成了1.22,why?

原因是1.23在處理過程中,因浮點數誤差實際上變成了1.229999999999999,乘以很大的倍數變成1229999999999999,多乘的需要再除回來,變成1.229999999999999,因為捨去剩兩位,最後剩下1.22。

為什麼做先乘後除這麼麻煩的事情呢?因為不知道哪來的概念,告訴我『乘以某個很大的倍數,再除回來,能夠縮小浮點數發生的誤差問題,乘以的數越大,則誤差會越小』。

這概念某程度上是對的,但不能解決浮點數四則運算會遇到的誤差問題,完全不能。

1.23,若有誤差發生,那可能會變成1.231或者1.229
如果是做乘大數發生的誤差,那數字可能變成1.230000000000001或1.229999999999999,相較起來,誤差範圍的確縮小很多。

但這對事情沒有幫助,至少無條件捨去的處理之下,狀況不會有改變。

而且起初我傻傻的以為,有發生誤差處理不好,是因為乘的數字不夠大,但這是錯誤的想法。

實際上什麼時候會發生浮點數誤差,其實不一定,也說不準,任何對浮點數的『四則運算』都有可能發生誤差。

例如某個浮點數可能乘以1000000會發生誤差,乘以100卻反而不會發生誤差,所以很難講,對於會不會發生這件事沒辦法有很好的處理或解決,乘大數再除回來,只能幫助發生誤差時,減少誤差的量級而已。

於是,非要做浮點數的四則運算,又要處理好誤差的問題,需要演算法和另外一種截然不同的處理機制。

最後我們將這個處理交由decimal套件處理,這是awesome-go推薦的套件,實際使用起來效能也沒有遇到顯著的影響,於是浮點數的運算後續都交由給它全權處理了。


上一篇
Day9 .[正確資料篇] range、map、slice、channel、goroutine 的組合應用
下一篇
Day11 .[正確資料篇] graceful shutdown & restart
系列文
Let's Eat GO ! 實務開發雜談by Golang30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言