Veo que, en C#, redondeando un decimal
, por defecto, usa MidpointRounding.ToEven
. Esto es lo que se espera, y es lo que dicta la especificación de C#. Sin embargo, dada la siguiente:¿Por qué .NET decimal.ToString (cadena) se aleja de cero, aparentemente inconsistente con la especificación de idioma?
- Un
decimal dVal
- Un formato
string sFmt
que, cuando se pasa adVal.ToString(sFmt)
, dará lugar a una cadena que contiene una versión redondeada dedVal
... es aparente que decimal.ToString(string)
devuelve un valor redondeado usando MidpointRounding.AwayFromZero
. Esto parece ser una contradicción directa de la especificación de C#.
Mi pregunta es esta: ¿Hay alguna buena razón para que este sea el caso? ¿O es esto solo una inconsistencia en el lenguaje?
A continuación, como referencia, he incluido un código que escribe para consolar una variedad de resultados de operación de redondeo y resultados de operación decimal.ToString(string)
, cada uno en cada valor en una matriz de valores decimal
. Las salidas reales están incrustadas. Después de eso, he incluido un párrafo relevante de la sección Especificación del lenguaje C# en el tipo decimal
.
El código de ejemplo:
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();
}
El párrafo de la sección 4.1.7 de la especificación del lenguaje C# ("El tipo decimal") (obtener la especificación completa here (.doc)):
El resultado de una operación en valores de tipo decimal es aquel que resultaría del cálculo de un resultado exacto (preservando la escala, como se define para cada operador) y luego redondeando para ajustarse a la representación. Los resultados se redondean al valor representable más cercano y, cuando un resultado es igualmente cercano a dos valores representables, al valor que tiene un número par en la posición de dígito menos significativo (esto se conoce como "redondeo bancario"). Un resultado cero siempre tiene un signo de 0 y una escala de 0.
Es fácil ver que pueden no haber estado considerando ToString(string)
en este párrafo, pero me inclino a pensar que cabe en esta descripción.
Es posible que deba considerar que C# no tiene el método 'ToString (string)'. El .NET Framework sí. No estoy seguro de que .NET Framework esté obligado a obedecer las reglas de cualquier lenguaje de programación en particular. –