前一封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 少,但可以表示較大範圍的值。

Post a comment