。NET decimal.ToString(string)为什么从零舍入,显然与语言规范不一致?

问题描述 投票:19回答:3

我看到,在C#中,默认情况下将decimal舍入为MidpointRounding.ToEven。这是预期的,也是C#规范要求的。但是,给出以下内容:

  • A 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格式字符串...

c# decimal tostring number-formatting rounding
3个回答
1
投票

ToString()默认情况下根据Culture进行格式化,而不是根据规范的计算方面进行格式化。显然,您所在地区的Culture(而且大多数情况是从外观上来看)都希望从零舍入。


6
投票

如果仔细阅读规范,将会发现这里没有矛盾之处。

这里是该段,重要部分突出显示:

对十进制类型的值的运算的结果


1
投票

最有可能是因为这是处理货币的标准方法。创建小数的推动力是浮点在处理货币值方面做得很差,因此您希望它的规则更符合会计标准,而不是数学正确性。

© www.soinside.com 2019 - 2024. All rights reserved.