我看到,在C#中,默认情况下将decimal
舍入为MidpointRounding.ToEven
。这是预期的,也是C#规范要求的。但是,给出以下内容:
decimal dVal
string sFmt
的格式dVal.ToString(sFmt)
,将导致包含dVal
的舍入版本的字符串>...显然,decimal.ToString(string)
返回使用MidpointRounding.AwayFromZero
取整的值。这似乎是C#规范的直接矛盾。
我的问题是:
是这样吗?还是这只是语言上的矛盾?[下面,作为参考,我包括一些代码,这些代码向控制台写入各种舍入运算结果和decimal.ToString(string)
运算结果,每个运算结果都针对decimal
值数组中的每个值。实际输出是嵌入式的。之后,我在decimal
类型中加入了C#语言规范部分的相关段落。
示例代码:
static void Main(string[] args) { decimal[] dArr = new decimal[] { 12.345m, 12.355m }; OutputBaseValues(dArr); // Base values: // d[0] = 12.345 // d[1] = 12.355 OutputRoundedValues(dArr); // Rounding with default MidpointRounding: // Math.Round(12.345, 2) => 12.34 // Math.Round(12.355, 2) => 12.36 // decimal.Round(12.345, 2) => 12.34 // decimal.Round(12.355, 2) => 12.36 OutputRoundedValues(dArr, MidpointRounding.ToEven); // Rounding with mr = MidpointRounding.ToEven: // Math.Round(12.345, 2, mr) => 12.34 // Math.Round(12.355, 2, mr) => 12.36 // decimal.Round(12.345, 2, mr) => 12.34 // decimal.Round(12.355, 2, mr) => 12.36 OutputRoundedValues(dArr, MidpointRounding.AwayFromZero); // Rounding with mr = MidpointRounding.AwayFromZero: // Math.Round(12.345, 2, mr) => 12.35 // Math.Round(12.355, 2, mr) => 12.36 // decimal.Round(12.345, 2, mr) => 12.35 // decimal.Round(12.355, 2, mr) => 12.36 OutputToStringFormatted(dArr, "N2"); // decimal.ToString("N2"): // 12.345.ToString("N2") => 12.35 // 12.355.ToString("N2") => 12.36 OutputToStringFormatted(dArr, "F2"); // decimal.ToString("F2"): // 12.345.ToString("F2") => 12.35 // 12.355.ToString("F2") => 12.36 OutputToStringFormatted(dArr, "###.##"); // decimal.ToString("###.##"): // 12.345.ToString("###.##") => 12.35 // 12.355.ToString("###.##") => 12.36 Console.ReadKey(); } private static void OutputBaseValues(decimal[] dArr) { Console.WriteLine("Base values:"); for (int i = 0; i < dArr.Length; i++) Console.WriteLine("d[{0}] = {1}", i, dArr[i]); Console.WriteLine(); } private static void OutputRoundedValues(decimal[] dArr) { Console.WriteLine("Rounding with default MidpointRounding:"); foreach (decimal d in dArr) Console.WriteLine("Math.Round({0}, 2) => {1}", d, Math.Round(d, 2)); foreach (decimal d in dArr) Console.WriteLine("decimal.Round({0}, 2) => {1}", d, decimal.Round(d, 2)); Console.WriteLine(); } private static void OutputRoundedValues(decimal[] dArr, MidpointRounding mr) { Console.WriteLine("Rounding with mr = MidpointRounding.{0}:", mr); foreach (decimal d in dArr) Console.WriteLine("Math.Round({0}, 2, mr) => {1}", d, Math.Round(d, 2, mr)); foreach (decimal d in dArr) Console.WriteLine("decimal.Round({0}, 2, mr) => {1}", d, decimal.Round(d, 2, mr)); Console.WriteLine(); } private static void OutputToStringFormatted(decimal[] dArr, string format) { Console.WriteLine("decimal.ToString(\"{0}\"):", format); foreach (decimal d in dArr) Console.WriteLine("{0}.ToString(\"{1}\") => {2}", d, format, d.ToString(format)); Console.WriteLine(); }
C#语言规范(“十进制类型”)第4.1.7节中的段落(获取完整的规范here(.doc)):
对十进制类型的值进行运算的结果是以下结果:计算精确结果(保留小数位数,为每个运算符定义),然后取整以适合表示形式。结果将四舍五入到最接近的可表示值,当结果同样接近两个可表示的值时,将其舍入到在最低有效数字位置上具有偶数的值(这称为“银行家舍入”)。零结果的总符号为0,小数位数为0。很容易看出他们可能没有在本段中考虑
ToString(string)
,但我倾向于认为它适合本描述。
我看到,在C#中,默认情况下将小数点后四舍五入使用MidpointRounding.ToEven。这是预期的,也是C#规范要求的。但是,给出以下信息:十进制dVal格式字符串...
ToString()
默认情况下根据Culture
进行格式化,而不是根据规范的计算方面进行格式化。显然,您所在地区的Culture
(而且大多数情况是从外观上来看)都希望从零舍入。
如果仔细阅读规范,将会发现这里没有矛盾之处。
这里是该段,重要部分突出显示:
对十进制类型的值的运算的结果
最有可能是因为这是处理货币的标准方法。创建小数的推动力是浮点在处理货币值方面做得很差,因此您希望它的规则更符合会计标准,而不是数学正确性。