TIPS-Float, Double, Decimal Calculation Performance
2 |
在前一封Post中我們討論過float, double, decimal的精確度問題,float的7位精確度在千萬時就破功了,double有15位,但如果要求算到6位小數,則整數有9位可用,數字一破百億就會有危險。不分青紅皂白一律用decimal如何?
這個提議有兩點要考量: 空間與時間。float, double, decimal所佔用的空間分別是4, 8, 16 Bytes, 除非是超大的陣列,否則每個變數多花8 Bytes來避免少一塊的惡夢看來很划算。至於速度呢? 我用以下的測試來驗證,分別對兩個int, float, double或decimal做23,000,000次加、減、乖除運算,看看所花的時間。
1: private void testInt(int v1, int v2, int times)
2: {
3: int x = v1, y = v2, z = 0;
4: for (int i = 0; i < times; i++)
5: {
6: z = x + y;
7: z = x - y;
8: z = x * y;
9: z = x / y;
10: }
11: }
12:
13: private void testFloat(float v1, float v2, int times)
14: {
15: float x = v1, y = v2, z = 0;
16: for (int i = 0; i < times; i++)
17: {
18: z = x + y;
19: z = x - y;
20: z = x * y;
21: z = x / y;
22: }
23: }
24:
25: private void testDouble(double v1, double v2, int times)
26: {
27: double x = v1, y = v2, z = 0;
28: for (int i = 0; i < times; i++)
29: {
30: z = x + y;
31: z = x - y;
32: z = x * y;
33: z = x / y;
34: }
35: }
36:
37: private void testDecimal(decimal v1, decimal v2, int times)
38: {
39: decimal x = v1, y = v2, z = 0;
40: for (int i = 0; i < times; i++)
41: {
42: z = x + y;
43: z = x - y;
44: z = x * y;
45: z = x / y;
46: }
47: }
48:
49: private double getTestDuration(int testNo, int times)
50: {
51: DateTime dt = DateTime.Now;
52: switch (testNo)
53: {
54: case 1:
55: testInt(500, 500, times);
56: break;
57: case 2:
58: testFloat(500, 500, times);
59: break;
60: case 3:
61: testDouble(500, 500, times);
62: break;
63: case 4:
64: testDecimal(500, 500, times);
65: break;
66: }
67: TimeSpan ts = DateTime.Now - dt;
68: return ts.TotalMilliseconds;
69: }
70:
71: protected void Page_Load(object sender, EventArgs e)
72: {
73: int TIMES = 10000000;
74: for (int i = 1; i <= 4; i++)
75: {
76: Response.Write(
77: string.Format("<li>Test {0} Duration={1:0.000}ms",
78: i, getTestDuration(i, TIMES)
79: )
80: );
81: }
82:
83: }
測試結果為:
- Test 1(int) Duration=140.618ms
- Test 2(float) Duration=343.732ms
- Test 3(double) Duration=343.732ms
- Test 4(decimal) Duration=7124.635ms
無庸置疑,int是最快的,但整數在帳務運算中沒啥大用。float與double的計算時間完全相同,所以,全面將float換成double並不會在效能上受到懲罰。至於精準無比的decimal,運算起來的速度比double慢了20倍,倒可以考慮一下是否值得。
【後話】
看完以上的Code,可能有些人會犯嘀咕,testInt, testFloat, testDouble, testDecimal同樣的Code寫四次,居然不會善用.NET 2.0新推的Generic(泛型),有什麼資格說自己是程式老鳥!!
事實上,我一開始當然是想耍帥的,順便用這個例子介紹一下Generic有多強! 但很遺憾地,Generic在這個例子裡發揮不了作用。我本來宣告了如下的測試用類別:
1: /// <summary>
2: /// 用泛型做個測試各型別運算效率的類別, 一整個帥氣呀
3: /// </summary>
4: /// <typeparam name="T">數字型別</typeparam>
5: class GenericCalcTester<T>
6: {
7: T x, y, z;
8: /// <summary>
9: /// 建構式,要給兩個初始值
10: /// </summary>
11: /// <param name="initX">初始值1</param>
12: /// <param name="initY">初始值2</param>
13: public GenericCalcTester(T initX, T initY)
14: {
15: x = initX;
16: y = initY;
17: }
18: /// <summary>
19: /// 進行測試,並傳回測試所花的時間(ms)
20: /// </summary>
21: /// <param name="times">執行計算的次數</param>
22: /// <returns>耗用時間(ms)</returns>
23: public double DoTest(int times)
24: {
25: DateTime dt = DateTime.Now;
26: for (int i = 0; i < times; i++)
27: {
28: T z = x + y;
29: z = x - y;
30: z = x * y;
31: z = x / y;
32: }
33: TimeSpan ts = DateTime.Now - ts;
34: return ts.TotalMilliseconds;
35: }
36: }
測試碼就可以寫成:
private void Test()
{
GenericCalcTester<int> intTest =
new GenericCalcTester<int>(500, 500);
double dura = intTest.DoTest(23000000);
GenericCalcTester<float> floatTest =
new GenericCalcTester<float>(500, 500);
dura = floatTest.DoTest(23000000);
}
很酷吧! 可惜這段Code連Build這關都過不了,會得到Operator '+' cannot be applied to operands of type 'T' and 'T'這個Error。
Google了一下,發現.NET為了嚴謹起見,在不確定所有的T都一定有實作+, -, *, /運算的顧慮下,索性禁止這種寫法(可以參考這篇文件)。CodeProject上有人提出一些解決方案(1, 2),但都頗為複雜,用在這個例子上有些模糊焦點。所以,最後決定用最直接的Copy & Paste法來呈現邏輯,大家忍耐一下吧!
Comments
# by fly
您好: 目前出現一個怪異的狀況,我宣告兩個變數,都是double,1390*0.35 出來的數字是486.499999999994 怎會這樣呢?不是486.5嗎?
# by Jeffrey
to fly, float與double都是浮點數字,因此其數值可能且容許能存在誤差,如果要求絕對精準(例如算錢),建議採用decimal。 引用MSDN文件 http://msdn.microsoft.com/zh-tw/library/ae55hdtk.aspx 裡的說明: Decimal 並非浮點數值資料型別。Decimal 數字包括二進位整數值,以及會指定數值的哪個部分為十進位分數的整數縮放比例。 浮點 (Single 和 Double) 數字的範圍比 Decimal 數字的範圍大,但會有捨入的誤差。浮點型別支援的有效位數比 Decimal 少,但可以表示較大範圍的值。