todecimal places number moneda formato convert c# decimal tostring number-formatting number-rounding

c# - places - ¿Por qué.NET decimal.ToString(cadena) se aleja de cero, aparentemente inconsistente con la especificación del idioma?



string.format c# number (3)

Muy probablemente porque esta es la forma estándar de tratar con la moneda. El ímpetu para la creación de un decimal fue que el punto flotante hace un mal trabajo al tratar con los valores de las monedas, por lo que cabe esperar que sus reglas estén más alineadas con los estándares de contabilidad que con la corrección matemática.

Veo que, en C #, redondear un decimal , por defecto, usa MidpointRounding.ToEven . Esto se espera, y es lo que dicta la especificación de C #. Sin embargo, dado lo siguiente:

  • Un decimal dVal
  • Una string sFmt formato string sFmt que, cuando se pasa a dVal.ToString(sFmt) , dará como resultado una cadena que contiene una versión redondeada de dVal

... es aparente que decimal.ToString(string) devuelve un valor redondeado usando MidpointRounding.AwayFromZero . Esto parecería ser una contradicción directa de la especificación C #.

Mi pregunta es la siguiente: ¿hay alguna buena razón para que sea así? ¿O es esto solo una inconsistencia en el lenguaje?

A continuación, como referencia, he incluido algunos códigos que escriben para consolar una variedad de resultados de operaciones de redondeo y resultados de operaciones de decimal.ToString(string) , cada uno en cada valor en una matriz de valores decimal . Las salidas reales están integradas. Después de eso, he incluido un párrafo relevante de la sección Especificación de 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") (obtenga la especificación completa here (.doc)):

El resultado de una operación en valores de tipo decimal es el que resultaría de calcular un resultado exacto (preservar la escala, como se define para cada operador) y luego redondear para ajustarse a la representación. Los resultados se redondean al valor representable más cercano y, cuando un resultado está igualmente cerca de dos valores representables, al valor que tiene un número par en la posición del 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 es posible que no hayan considerado ToString(string) en este párrafo, pero me inclino a pensar que encaja en esta descripción.


Si lees la especificación cuidadosamente, verás que no hay inconsistencia aquí.

Aquí está ese párrafo de nuevo, con las partes importantes resaltadas:

El resultado de una operación en valores de tipo decimal es el que resultaría de calcular un resultado exacto (preservar la escala, como se define para cada operador) y luego redondear para ajustarse a la representación. Los resultados se redondean al valor representable más cercano y, cuando un resultado está igualmente cerca de dos valores representables, al valor que tiene un número par en la posición del dígito menos significativo (esto se conoce como "redondeo bancario"). Un resultado cero siempre tiene un signo de 0 y una escala de 0.

Esta parte de la especificación se aplica a las operaciones aritméticas en decimal ; el formato de cadena no es uno de esos, e incluso si lo fuera, no importaría porque sus ejemplos son de baja precisión.

Para demostrar el comportamiento referido en la especificación, use el siguiente código:

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);

Eso es todo de lo que se habla. Si el resultado de algún cálculo excediera el límite de precisión del tipo decimal (29 dígitos), se utiliza el redondeo del banco para determinar cuál será el resultado.


ToString() por defecto, según los formatos de la Culture , no de acuerdo con un aspecto computacional de la especificación. Al parecer, la Culture para su localidad (y la mayoría, por su aspecto) espera redondearse desde cero.

Si desea un comportamiento diferente, puede pasar un IFormatProvider a ToString()

Pensé lo anterior, pero tienes razón en que siempre se redondea desde cero, sin importar la Culture . Compruebe los enlaces en los comentarios para la prueba.