消除Decimal型別尾數多餘的補零

Decimal型別用來處理數值好處多多,但是有一個有可能不太適用於真實社會上的處理特性,那就是當你對decimal進行ToString()後,輸出會幫忙補足該數值的浮點位數,這點有時候還蠻惱人的。(會計酸:謝謝你幫我補那堆無意義的零喔!)

Decimal型別遇到浮點型態尾數會自動補零的特性

假設我們有一個decimal型別的程式碼這樣寫:

var fTest = 1.000001m;
Console.WriteLine(fTest);
fTest += 0.999999m;
Console.WriteLine(fTest);

則輸出會變成:

1.000001
2.000000

會形成這樣的原因,是因為decimal fTest會自始至終想要保持它的精度,但是有時候我們並不會想要2.000000這樣的結果,反而會是想要2來表示就好。

再舉例如下方較為完整的程式碼,我們將每一次的decimal運算都加入到LIST之中:

static void Main(string[] args)
{
  var oResult = new System.Collections.Generic.List<object>();
  decimal fCount = 1;
  for (int i = 0; i < 10; i++)
  {
    fCount += 0.01m;
    oResult.Add(new { fPrice = fCount });
  }
  Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(oResult, Newtonsoft.Json.Formatting.Indented));
  Console.Read();
}

輸出與我們要關注的問題發生點如下:

[
  {
    "fPrice": 1.01
  },
  {
    "fPrice": 1.02
  },
  {
    "fPrice": 1.03
  },
  {
    "fPrice": 1.04
  },
  {
    "fPrice": 1.05
  },
  {
    "fPrice": 1.06
  },
  {
    "fPrice": 1.07
  },
  {
    "fPrice": 1.08
  },
  {
    "fPrice": 1.09
  },
  {
    "fPrice": 1.10 ←我們想要變成1.1就好
  }
]

解決方式

最快的解決方式很奇葩,因為decimal的精密度是28-29位數,所以就是把答案除以1後面帶大於或等於28個零就好了。以上面的例子可以將程式碼修正如下:

static void Main(string[] args)
{
  var oResult = new System.Collections.Generic.List<object>();
  decimal fCount = 1;
  for (int i = 0; i < 10; i++)
  {
    fCount += 0.01m;
    oResult.Add(new { fPrice = fCount / 1.0000000000000000000000000000m });
  }
  Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(oResult, Newtonsoft.Json.Formatting.Indented));
  Console.Read();
}

輸出的結果就修正成為我們想要的樣子了:

[
  {
    "fPrice": 1.01
  },
  {
    "fPrice": 1.02
  },
  {
    "fPrice": 1.03
  },
  {
    "fPrice": 1.04
  },
  {
    "fPrice": 1.05
  },
  {
    "fPrice": 1.06
  },
  {
    "fPrice": 1.07
  },
  {
    "fPrice": 1.08
  },
  {
    "fPrice": 1.09
  },
  {
    "fPrice": 1.1 ←變成我們想要的樣子了,尾數的零消失了
  }
]

補充說明

透過字串的尋找來解決尾數補零也是一個方法,但程式碼會稍微囉嗦一點。另外其他試圖採用parse或是透過StringFormat之類的"0.###"轉換,都請銘記一件事情,你正在試圖轉型,所以請把進位取捨考慮進去,維持decimal的精確度讓這問題沒有那麼簡單。

CSharp Decimal ToString Json.NET SerializeObject RemoveTrailingZeros