Saltar al contenido

¿Por qué Math.Round (2.5) devuelve 2 en lugar de 3?

Te sugerimos que pruebes esta respuesta en un entorno controlado antes de pasarlo a producción, un saludo.

Solución:

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 Math.Round está implementado.

Y en segundo lugar, no: si lee los documentos, verá que el redondeo predeterminado es “redondear a par” (redondeo bancario):

Valor de retorno
Tipo: Sistema.Doble
El entero más cercano a a. Si el componente fraccionario de a está a medio camino entre dos enteros, uno de los cuales es par y el otro impar, entonces se devuelve el número par. Tenga en cuenta que este método devuelve un Double en lugar de un tipo integral.

Observaciones
El comportamiento de este método sigue el estándar IEEE 754, sección 4. Este tipo de redondeo a veces se denomina redondeo al más cercano o redondeo bancario. Minimiza los errores de redondeo que resultan del redondeo constante de un valor de punto medio en una sola dirección.

Puede especificar cómo Math.Round debe redondear los puntos medios usando una sobrecarga que toma un MidpointRounding valor. Hay una sobrecarga con un MidpointRounding correspondiente a cada una de las sobrecargas que no la tiene:

  • 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 manera fácil de implementar el comportamiento deseado sin hacerlo usted mismo). En particular, la historia ha demostrado que no es el previsto comportamiento – y en la mayoría de los casos eso es un pecado capital en el diseño de API. Puedo ver por qué Banker’s Rounding es útil… pero sigue siendo una sorpresa para muchos.

Puede que le interese 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).

Eso 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 el redondeo a par minimiza eso) (a).

Siga estos enlaces para ver las descripciones de MSDN de:

  • Math.Floorque redondea hacia abajo hacia el infinito negativo.
  • Math.Ceilingque redondea hacia infinito positivo.
  • Math.Truncateque redondea hacia arriba o hacia abajo hacia cero.
  • Math.Round, que redondea al 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 último dígito sea par (“Round(2.5,MidpointRounding.ToEven)” convirtiéndose en 2) o para que se aleje más de cero (“Round(2.5,MidpointRounding.AwayFromZero)” convertirse 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, debe usar trucos 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 pares (por ejemplo), los errores se acumularán tan rápido como si siempre redondeara hacia arriba.

Desde MSDN, Math.Round(doble a) devuelve:

El entero más cercano a a. Si el componente fraccionario de a está a medio camino entre dos enteros, uno de los cuales es par y el otro impar, entonces se devuelve el número par.

… y así 2,5, estando a medio 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 de uso común.

Mismo artículo de MSDN:

El comportamiento de este método sigue el estándar IEEE 754, sección 4. Este tipo de redondeo a veces se denomina redondeo al más cercano o redondeo bancario. Minimiza los errores de redondeo que resultan del redondeo constante de 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 MidpointRounding modo.

Si te ha sido útil este artículo, agradeceríamos que lo compartas con otros seniors de esta forma contrubuyes a extender esta información.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *