KB-.NET Math Quiz
1 |
1: protected void Page_Load(object sender, EventArgs e)
2: {
3: double x = Math.Round(2.5);
4: double y = Math.Round(3.5);
5: Response.Write(
6: string.Format("<li>x={0} y={1}",
7: x, y)
8: );
9: float f = 5000001;
10: float g = 5000002;
11: float h = f + g;
12: Response.Write(
13: string.Format("<li>{0:#,#} + {1:#,#} = {2:#,#}",
14: f, g, h)
15: );
16: }
今天來談談.NET中的數學,以上的程式碼裡有兩題算數,請用眼睛Build & Run,再告訴我答案。
真簡單,答案分別是x=3 y=4、5,000,001 + 5,000,002 = 10,000,003,問這種小學生都會的問題,似乎有污辱大家智慧的嫌疑。
事情當然沒這麼簡單,如果你真的跑一下上述的Code,會懷疑電腦是不是瘋了...
這裡有兩個陷阱:
1) 大家看到Math.Round,應該會認為它跟Javascript中的Math.round,T-SQL/PL-SQL中的Round函數一樣,就是四捨"五"入唄。逄5要進位連小學生都知道,但為什麼Math.Round(2.5)=2? 仔細看一下Math.Round的函數說明,你會看到這一句話: 最接近參數 d 的整數。如果 d 正好為兩個整數的中間數 (一個為偶數,另一個為奇數),則會傳回偶數。這是所謂的銀行家捨入法(Round-To-Even,也叫做Bankers' Rounding,洋人銀行家不知是否也像中國人一樣,講究好事成雙,迷信起雙數才吉利這一套,四捨六入五成雙的規則可以縮小先捨入再累加的計算誤差,例如:1.5+2.5=4,若用四捨五入2+3=5,而四捨六入五成雙則是2+2=4,更接近原值。[感謝網友楊樹人補充] 事實上VB、VBScript以來就是依循這個準則做捨入。) 如果希望.NET用我們從小熟悉的法則做四捨五入,可以加上MidpointRounding.AwayFromZero參數,例如: Math.Round(2.5, MidpointRounding.AwayFromZero)就會得到3。
2) 第二個陷阱是關於float,依照文件的說明,它的精確度只有七位! 也就是說當你在float變數中塞入八位以上的數字,系統不會出錯,但第七位之後的數字會悄悄地蒸發掉。理論上一千萬只差個位數應該影響有限,但做過帳務系統的人都知道"差一元"的威力,它常常讓一群人七晚八晚回不了家,神情慌亂,披頭散髮,滿身大汗,狼狽不堪,上窮碧落下黃泉,只為了找出少掉的一塊錢藏在哪裡... 總之,如果你的程式要運算鉅額的金錢數字,最好乖乖用double(精確度15-16位)或乾脆用decimal(28-29位)算了。如果要做到一分一毫都不容有誤,decimal才是王道,勿用浮點(float、double),請記口訣:「算數用浮點,遲早被人扁」。
Comments
# by wayne
Thanks, very helpful!