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

标签 c# decimal tostring number-formatting rounding

我看到,在 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)):

The result of an operation on values of type decimal is that which would result from calculating an exact result (preserving scale, as defined for each operator) and then rounding to fit the representation. Results are rounded to the nearest representable value, and, when a result is equally close to two representable values, to the value that has an even number in the least significant digit position (this is known as “banker’s rounding”). A zero result always has a sign of 0 and a scale of 0.

很容易看出他们可能没有考虑过 ToString(string)在本段中,但我倾向于认为它符合此描述。

最佳答案

如果你仔细阅读规范,你会发现这里没有不一致之处。

这里又是那个段落,突出了重要的部分:

The result of an operation on values of type decimal is that which would result from calculating an exact result (preserving scale, as defined for each operator) and then rounding to fit the representation. Results are rounded to the nearest representable value, and, when a result is equally close to two representable values, to the value that has an even number in the least significant digit position (this is known as “banker’s rounding”). A zero result always has a sign of 0 and a scale of 0.

规范的这一部分适用于十进制算术运算;字符串格式不是其中之一,即使是,也没关系,因为您的示例是低精度的。

要演示规范中提到的行为,请使用以下代码:

Decimal d1 = 0.00000000000000000000000000090m;
Decimal d2 = 0.00000000000000000000000000110m;

// Prints: 0.0000000000000000000000000004 (rounds down)
Console.WriteLine(d1 / 2);

// Prints: 0.0000000000000000000000000006 (rounds up)
Console.WriteLine(d2 / 2);

这就是规范所讨论的全部内容。如果某些计算的结果会超过 decimal 类型(29 位)的精度限制,则使用银行四舍五入法来确定结果。

关于c# - 为什么 .NET decimal.ToString(string) 从零舍入,显然与语言规范不一致?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2249279/

相关文章:

c# - 遍历数组 - 获取所有其他项目?

java - 打印二维数组列表

java - 为什么在 java src 中 Integer 类的 toString 方法中使用负 int 进行 mod 操作

java - 截断为整数...以及 if 语句确定它是否不是整数

java - 比较浮点值在 java 中给出意想不到的结果

php - 小数赔率转换超时

java.util.ConcurrentModificationException 尝试使用迭代器时出错

c# - 在 MessagePack 中将具有接口(interface)作为属性类型的对象序列化

c# - 转到另一页时,在单击按钮时播放声音C#

c# - ListView 中选中项的颜色