本人在C#中進行小數舍入的時候經常會懷念Excel中的Round、RoundUp、RoundDown這幾個函數,緣由就是後者「接地氣」,比較符合俺小老百姓的舍入要求,啥「銀行家舍入法」就讓銀行家用去吧。今兒有空,就把它實現了一下,先溫習一下這幾個Excel函數的功能:html
Round(value, digits)git
將value按四捨五入法進行舍入,保留digits位小數;當digits爲負時,在小數點左側進行舍入;當value爲負時,表現與正數徹底相反。函數
舉例:Round(3.145, 2) = 3.15;Round(-3.145, 2) = -3.15;Round(3145, -2) = 3100性能
RoundUp(value, digits)測試
按遠離 0 的方向,將value向上舍入,保留digits位小數;當digits爲負時,在小數點左側進行舍入this
舉例:RoundUp(3.111, 2) = 3.12;RoundUp(-3.111, 2) = -3.12;RoundUp(3111, -2) = 3200spa
RoundDown(value, digits).net
按靠近 0 的方向,將value向下舍入,保留digits位小數;當digits爲負時,在小數點左側進行舍入code
舉例:RoundDown(3.145, 2) = 3.14;RoundDown(-3.145, 2) = -3.14;RoundDown(3145, -2) = 3100htm
實現原理:
- 對於RoundUp和RoundDown,因爲decimal或Math類的Ceiling和Floor方法(下稱C/F)只能取整,因此先根據要保留的位數,乘除獲得可供C/F方法發揮的新值,而後就能夠利用C/F獲得舍入後的值,再乘/除回去,獲得最終結果。此法市面常見。
舉例:1.114向上保留2位,首先1.114x100獲得111.4,再用C(111.4)獲得112,而後112 / 100,最終獲得1.12
問題:因爲要先對原值進行乘除,因此對於接近Max/Min、或精度太高的原值,這一步就會形成溢出,因此Up和Down不能應對特別大的值,但平常應用相信沒問題。
- 對於RoundEx方法,則直接封裝decimal.Round(decimal, MidpointRounding.AwayFromZero)獲得結果。
實現說明:
- 以擴展方法提供,兼容常規方法調用方式(廢話)。便可以3.145M.RoundEx(2),也能夠MathEx.RoundEx(3.145M, 2)
- 每一個方法以decimal和double兩種類型提供重載,共6個方法
- 以decimal類型爲基礎進行實現,double版只是重用+類型轉換。之因此不對double進行實現,不是由於偷懶,而是由於浮點運算容易扯蛋,如555.55x100=55554.999999999993。關於浮點運算的不可靠性,可參看:http://www.cnblogs.com/ethancai/articles/1237012.html
- 四捨五入函數命名爲RoundEx是由於decimal類已經存在一個叫Round的靜態方法,若是不錯開,將不能以擴展方式3M.Round()進行調用。並且雖然.net在命名上具備極大的包容度,但我認爲仍是儘可能避開FCL命名的好,無謂去「享受」這種自由度
- 幾個方法之因此都要先判斷一下保留位數,而沒有直接使用10的digits次方進行運算,是想盡可能沿用decimal類型的原生方法,減小不必的數學運算。咱追求的不是極簡的代碼,而是性能。固然,沒測試過~雞蛋飛來中...
廢話了一堆,上代碼:
/// <summary>
/// 數學類擴展方法 /// </summary>
public static class MathEx { /// <summary>
/// 遠離 0 向上舍入 /// </summary>
public static decimal RoundUp(this decimal value, sbyte digits) { if (digits == 0) { return (value >= 0 ? decimal.Ceiling(value) : decimal.Floor(value)); } decimal multiple = Convert.ToDecimal(Math.Pow(10, digits)); return (value >= 0 ? decimal.Ceiling(value * multiple) : decimal.Floor(value * multiple)) / multiple; } /// <summary>
/// 靠近 0 向下舍入 /// </summary>
public static decimal RoundDown(this decimal value, sbyte digits) { if (digits == 0) { return (value >= 0 ? decimal.Floor(value) : decimal.Ceiling(value)); } decimal multiple = Convert.ToDecimal(Math.Pow(10, digits)); return (value >= 0 ? decimal.Floor(value * multiple) : decimal.Ceiling(value * multiple)) / multiple; } /// <summary>
/// 四捨五入 /// </summary>
public static decimal RoundEx(this decimal value, sbyte digits) { if (digits >= 0) { return decimal.Round(value, digits, MidpointRounding.AwayFromZero); } decimal multiple = Convert.ToDecimal(Math.Pow(10, -digits)); return decimal.Round(value / multiple, MidpointRounding.AwayFromZero) * multiple; } /// <summary>
/// 遠離 0 向上舍入 /// </summary>
public static double RoundUp(this double value, sbyte digits) { return decimal.ToDouble(Convert.ToDecimal(value).RoundUp(digits)); } /// <summary>
/// 靠近 0 向下舍入 /// </summary>
public static double RoundDown(this double value, sbyte digits) { return decimal.ToDouble(Convert.ToDecimal(value).RoundDown(digits)); } /// <summary>
/// 四捨五入 /// </summary>
public static double RoundEx(this double value, sbyte digits) { return decimal.ToDouble(Convert.ToDecimal(value).RoundEx(digits)); } }
- 文畢 -