.net - redondear - round c# double
¿Por qué Math.Round(2.5) devuelve 2 en lugar de 3? (15)
En C #, el resultado de Math.Round(2.5)
es 2.
Se supone que es 3, ¿no? ¿Por qué es 2 en lugar de C #?
La naturaleza del redondeo.
Considere la tarea de redondear un número que contiene una fracción a, digamos, un número entero. El proceso de redondeo en esta circunstancia es determinar qué número entero representa mejor el número que está redondeando.
En redondeo común, o ''aritmético'', está claro que 2.1, 2.2, 2.3 y 2.4 redondean a 2.0; y 2.6, 2.7, 2.8 y 2.9 a 3.0.
Eso deja 2.5, que no está más cerca de 2.0 que de 3.0. Depende de usted elegir entre 2.0 y 3.0, cualquiera de los dos sería igualmente válido.
Para números negativos, -2.1, -2.2, -2.3 y -2.4, se convertiría en -2.0; y -2.6, 2.7, 2.8 y 2.9 se convertirían en -3.0 en el redondeo aritmético.
Para -2.5 se necesita una opción entre -2.0 y -3.0.
Otras formas de redondeo
''Redondear'' toma cualquier número con posiciones decimales y lo convierte en el siguiente número ''entero''. Por lo tanto, no solo se redondean 2.5 y 2.6 a 3.0, sino también 2.1 y 2.2.
Al redondear, los números positivos y negativos se alejan de cero. P.ej. 2.5 a 3.0 y -2.5 a -3.0.
''Redondear hacia abajo'' trunca los números al cortar los dígitos no deseados. Esto tiene el efecto de mover los números hacia cero. P.ej. 2.5 a 2.0 y -2.5 a -2.0
En el "redondeo bancario", en su forma más común, el .5 a redondear se redondea hacia arriba o hacia abajo para que el resultado del redondeo sea siempre un número par. Por lo tanto, 2.5 rondas a 2.0, 3.5 a 4.0, 4.5 a 4.0, 5.5 a 6.0, y así sucesivamente.
''Redondeo alternativo'' alterna el proceso para cualquier .5 entre redondear hacia abajo y redondear hacia arriba.
El "redondeo aleatorio" redondea un .5 hacia arriba o hacia abajo de forma totalmente aleatoria.
Simetría y asimetría
Se dice que una función de redondeo es ''simétrica'' si redondea todos los números desde cero o redondea todos los números hacia cero.
Una función es ''asimétrica'' si redondea los números positivos hacia cero y los números negativos se alejan de cero. Por ejemplo, 2.5 a 2.0; y -2.5 a -3.0.
También es asimétrica una función que redondea los números positivos desde cero y los números negativos hacia cero. P.ej. 2.5 a 3.0; y -2.5 a -2.0.
La mayoría de las personas piensan en el redondeo simétrico, donde -2.5 se redondeará hacia -3.0 y 3.5 se redondeará hacia 4.0. (en C # Round(AwayFromZero)
)
Debe comprobar MSDN para Math.Round
:
El comportamiento de este método sigue la norma IEEE 754, sección 4. Este tipo de redondeo a veces se llama redondeo al redondeo más cercano o bancario.
Puede especificar el comportamiento de Math.Round
utilizando una sobrecarga:
Math.Round(2.5, 0, MidpointRounding.AwayFromZero); // gives 3
Math.Round(2.5, 0, MidpointRounding.ToEven); // gives 2
Desde MSDN, Math.Round (double a) devuelve:
El entero más cercano a. Si el componente fraccional de a está a medio camino entre dos enteros, uno de los cuales es par y el otro impar, se devuelve el número par.
... y así, 2.5, estando a mitad de camino entre 2 y 3, se redondea al número par (2). esto se denomina redondeo bancario (o redondeo a par), y es un estándar de redondeo comúnmente utilizado.
Mismo artículo de MSDN:
El comportamiento de este método sigue la norma IEEE 754, sección 4. Este tipo de redondeo a veces se llama redondeo al redondeo más cercano o bancario. Minimiza los errores de redondeo que resultan de redondear constantemente un valor de punto medio en una sola dirección.
Puede especificar un comportamiento de redondeo diferente llamando a las sobrecargas de Math.Round que toman un modo de redondeo de punto MidpointRounding
.
Desde MSDN:
De forma predeterminada, Math.Round utiliza MidpointRounding.ToEven. La mayoría de las personas no están familiarizadas con el "redondeo para igualar" como la alternativa, "redondear desde cero" se enseña más comúnmente en la escuela. El valor predeterminado de .NET es "Redondear a nivelado", ya que es estadísticamente superior porque no comparte la tendencia de "redondear desde cero" a redondearse ligeramente más a menudo de lo que se redondea (asumiendo que los números que se redondean tienden a ser positivos )
El MidpointRounding.ToEven
predeterminado, o el redondeo de los Banqueros ( 2.5 se convierten en 2, 4.5 se convierte en 4 y así sucesivamente ) me ha picado antes con la redacción de informes para la contabilidad, por lo que escribiré algunas palabras de lo que descubrí, anteriormente en él para este post.
¿Quiénes son estos banqueros que se están redondeando en números pares (quizás los banqueros británicos)?
De wikipedia
El origen del término redondeo de los banqueros sigue siendo más oscuro. Si este método de redondeo alguna vez fue un estándar en la banca, la evidencia ha resultado extremadamente difícil de encontrar. Por el contrario, la sección 2 del informe de la Comisión Europea La introducción del euro y el redondeo de los montos en moneda sugiere que anteriormente no había habido un enfoque estándar para el redondeo en la banca; y especifica que las cantidades "a mitad de camino" deben redondearse.
Parece una forma muy extraña de redondear particularmente para la banca, a menos que, por supuesto, los bancos usen para recibir muchos depósitos de cantidades iguales. Deposite £ 2.4m, pero lo llamaremos £ 2m señor.
El estándar IEEE 754 se remonta a 1985 y ofrece ambas formas de redondeo, pero con el estándar bancario recomendado por el estándar. Este artículo de wikipedia tiene una larga lista de cómo los lenguajes implementan el redondeo (corríjame si alguno de los siguientes puntos es incorrecto) y la mayoría no usa los banqueros, pero el redondeo que se enseña en la escuela:
- C / C ++ round () de math.h redondea fuera de cero (no redondeo bancario)
- Java Math.Round redondea fuera de cero ( Math.Round el resultado, agrega 0.5, convierte a un entero). Hay una alternativa en BigDecimal
- Perl usa una forma similar a C
- Javascript es el mismo que el Math.Round de Java.
En primer lugar, esto no sería un error de C # de todos modos, sería un error de .NET. C # es el lenguaje, no decide cómo se implementa Math.Round
.
Y en segundo lugar, no: si lees los documentos , verás que el redondeo predeterminado es "redondear a par" (redondeo bancario):
Valor de retorno
Tipo: System.Double
El entero más cercano a. Si el componente fraccional de a está a medio camino entre dos enteros, uno de los cuales es par y el otro impar, se devuelve el número par. Tenga en cuenta que este método devuelve un tipoDouble
lugar de un tipo integral.Observaciones
El comportamiento de este método sigue la norma IEEE 754, sección 4. Este tipo de redondeo a veces se llama redondeo al redondeo más cercano o bancario. Minimiza los errores de redondeo que resultan de redondear constantemente un valor de punto medio en una sola dirección.
Puede especificar cómo Math.Round
debe redondear los puntos medios utilizando una sobrecarga que toma un valor de MidpointRounding
. Hay una sobrecarga con un MidpointRounding
correspondiente a cada una de las sobrecargas que no tiene una:
-
Round(Decimal)
/Round(Decimal, MidpointRounding)
-
Round(Double)
/Round(Double, MidpointRounding)
-
Round(Decimal, Int32)
/Round(Decimal, Int32, MidpointRounding)
-
Round(Double, Int32)
/Round(Double, Int32, MidpointRounding)
Si este valor predeterminado fue bien elegido o no es un asunto diferente. ( MidpointRounding
solo se introdujo en .NET 2.0. Antes de eso, no estoy seguro de que hubiera una forma fácil de implementar el comportamiento deseado sin hacerlo usted mismo). En particular, la historia ha demostrado que no es el comportamiento esperado , y en la mayoría de los casos Eso es un pecado cardinal en el diseño API. Puedo ver por qué Banker''s Rounding es útil ... pero sigue siendo una sorpresa para muchos.
Puede interesarle echar un vistazo a la enumeración equivalente de Java más cercana ( RoundingMode
) que ofrece aún más opciones. (No solo se ocupa de los puntos medios.)
Esta es la forma en que tuve que trabajar alrededor:
Public Function Round(number As Double, dec As Integer) As Double
Dim decimalPowerOfTen = Math.Pow(10, dec)
If CInt(number * decimalPowerOfTen) = Math.Round(number * decimalPowerOfTen, 2) Then
Return Math.Round(number, 2, MidpointRounding.AwayFromZero)
Else
Return CInt(number * decimalPowerOfTen + 0.5) / 100
End If
End Function
¡Probar con 1.905 con 2 decimales dará 1.91 como se esperaba, pero Math.Round(1.905,2,MidpointRounding.AwayFromZero)
da 1.90! El método Math.Round es absolutamente inconsistente e inutilizable para la mayoría de los problemas básicos que pueden encontrar los programadores. Tengo que verificar si (int) 1.905 * decimalPowerOfTen = Math.Round(number * decimalPowerOfTen, 2)
porque no quiero redondear lo que debería ser redondeado hacia abajo.
Este post tiene la respuesta que estás buscando:
http://weblogs.asp.net/sfurman/archive/2003/03/07/3537.aspx
Básicamente esto es lo que dice:
Valor de retorno
El valor más cercano al número con una precisión igual a los dígitos. Si el valor está a medio camino entre dos números, uno de los cuales es par y el otro impar, entonces se devuelve el número par. Si la precisión del valor es menor que los dígitos, el valor se devuelve sin cambios.
El comportamiento de este método sigue la norma IEEE 754, sección 4. Este tipo de redondeo a veces se llama redondeo al redondeo más cercano o bancario. Si los dígitos son cero, este tipo de redondeo a veces se llama redondeo hacia cero.
Esto es feo como todo el infierno, pero siempre produce el redondeo aritmético correcto.
public double ArithRound(double number,int places){
string numberFormat = "###.";
numberFormat = numberFormat.PadRight(numberFormat.Length + places, ''#'');
return double.Parse(number.ToString(numberFormat));
}
Esto se llama redondeo a par (o redondeo bancario), que es una estrategia de redondeo válida para minimizar los errores acumulados en las sumas (MidpointRounding.ToEven)
. La teoría es que, si siempre redondeas un número 0.5 en la misma dirección, los errores se acumularán más rápido (se supone que redondear a par minimiza eso) (a) .
Siga estos enlaces para las descripciones de MSDN de:
-
Math.Floor
, que se redondea hacia el infinito negativo. -
Math.Ceiling
, que se redondea hacia el infinito positivo. -
Math.Truncate
, que se redondea hacia arriba o hacia abajo hasta cero. -
Math.Round
, que se redondea al número entero más cercano o al número especificado de lugares decimales. Puede especificar el comportamiento si es exactamente equidistante entre dos posibilidades, como redondear para que el dígito final sea par ("Round(2.5,MidpointRounding.ToEven)
" se convierta en 2) o en que esté más alejado de cero ("Round(2.5,MidpointRounding.AwayFromZero)
"se convierte en 3).
El siguiente diagrama y tabla pueden ayudar:
-3 -2 -1 0 1 2 3
+--|------+---------+----|----+--|------+----|----+-------|-+
a b c d e
a=-2.7 b=-0.5 c=0.3 d=1.5 e=2.8
====== ====== ===== ===== =====
Floor -3 -1 0 1 2
Ceiling -2 0 1 2 3
Truncate -2 0 0 1 2
Round(ToEven) -3 0 0 2 3
Round(AwayFromZero) -3 -1 0 2 3
Tenga en cuenta que Round
es mucho más poderoso de lo que parece, simplemente porque puede redondear a un número específico de lugares decimales. Todos los demás redondean a cero decimales siempre. Por ejemplo:
n = 3.145;
a = System.Math.Round (n, 2, MidpointRounding.ToEven); // 3.14
b = System.Math.Round (n, 2, MidpointRounding.AwayFromZero); // 3.15
Con las otras funciones, tienes que usar el truco de multiplicar / dividir para lograr el mismo efecto:
c = System.Math.Truncate (n * 100) / 100; // 3.14
d = System.Math.Ceiling (n * 100) / 100; // 3.15
(a) Por supuesto, esa teoría depende del hecho de que sus datos tienen una distribución de valores bastante uniforme entre las mitades pares (0.5, 2.5, 4.5, ...) y las mitades impares (1.5, 3.5, ...).
Si todos los "valores medios" son iguales (por ejemplo), los errores se acumularán tan rápido como si siempre se redondeara.
La forma simple es:
Math.Ceiling(decimal.Parse(yourNumber + ""));
Silverlight no es compatible con la opción MidpointRounding. Aquí hay un método de extensión para Silverlight que agrega la enumeración MidpointRounding:
public enum MidpointRounding
{
ToEven,
AwayFromZero
}
public static class DecimalExtensions
{
public static decimal Round(this decimal d, MidpointRounding mode)
{
return d.Round(0, mode);
}
/// <summary>
/// Rounds using arithmetic (5 rounds up) symmetrical (up is away from zero) rounding
/// </summary>
/// <param name="d">A Decimal number to be rounded.</param>
/// <param name="decimals">The number of significant fractional digits (precision) in the return value.</param>
/// <returns>The number nearest d with precision equal to decimals. If d is halfway between two numbers, then the nearest whole number away from zero is returned.</returns>
public static decimal Round(this decimal d, int decimals, MidpointRounding mode)
{
if ( mode == MidpointRounding.ToEven )
{
return decimal.Round(d, decimals);
}
else
{
decimal factor = Convert.ToDecimal(Math.Pow(10, decimals));
int sign = Math.Sign(d);
return Decimal.Truncate(d * factor + 0.5m * sign) / factor;
}
}
}
Fuente: http://anderly.com/2009/08/08/silverlight-midpoint-rounding-solution/
Tuve este problema donde mi servidor SQL redondea 0.5 a 1 mientras que mi aplicación C # no lo hizo. Así que verías dos resultados diferentes.
Aquí hay una implementación con int / long. Así es como se redondea Java.
int roundedNumber = (int)Math.Floor(d + 0.5);
Probablemente sea el método más eficiente que puedas imaginar.
Si quieres mantenerlo doble y usar precisión decimal, entonces solo es cuestión de usar exponentes de 10 en función de la cantidad de decimales.
public double getRounding(double number, int decimalPoints)
{
double decimalPowerOfTen = Math.Pow(10, decimalPoints);
return Math.Floor(number * decimalPowerOfTen + 0.5)/ decimalPowerOfTen;
}
Puede ingresar un decimal negativo para los puntos decimales y también está bien la palabra.
getRounding(239, -2) = 200
Ya que Silverlight no admite la opción MidpointRounding, tiene que escribir la suya propia. Algo como:
public double RoundCorrect(double d, int decimals)
{
double multiplier = Math.Pow(10, decimals);
if (d < 0)
multiplier *= -1;
return Math.Floor((d * multiplier) + 0.5) / multiplier;
}
Para los ejemplos que incluyen cómo usar esto como una extensión, vea la publicación: .NET y Silverlight Rounding
utilizando un redondeo personalizado
public int Round(double value)
{
double decimalpoints = Math.Abs(value - Math.Floor(value));
if (decimalpoints > 0.5)
return (int)Math.Round(value);
else
return (int)Math.Floor(value);
}