Redondeo al conjunto en .NET

¡Todos ku barbudos , camaradas!

Todos sabemos lo que es redondear. Si alguien lo olvidó, entonces el redondeo es el reemplazo de un número con su valor aproximado, escrito con menos dígitos significativos. Si le pregunta a una persona sobre la marcha qué sucede cuando redondea 6.5 a enteros, responderá "7" sin dudarlo. La escuela nos enseñó que los números se redondean al número entero más cercano, que es un número mayor en valor absoluto. Es decir, si en el número redondeado la parte fraccionaria es igual o más de la mitad de la descarga de la parte completa, entonces redondeamos el número original al más grande más cercano.

En pocas palabras:
6,4 = 6 6,5 = 7 6,6 = 7 
etc.

Y así, al salir de la escuela y convertirnos en programadores, a menudo esperamos el mismo comportamiento de nuestros poderosos lenguajes de programación. Olvidando por completo que en la escuela nos enseñaron "redondeo matemático", pero en realidad hay muchos más tipos de redondeo. Solo en Wikipedia, puede cavar cuántas opciones de redondeo son 0.5 al entero más cercano:

  • Redondeo matemático
  • Redondeo aleatorio
  • Redondeo alterno
  • Redondeo bancario

El primer tipo, "redondeo matemático", todos lo aprendimos de la escuela. Puede leer sobre el segundo y el tercer tipo en su tiempo libre, hoy no me interesan en este artículo.

Pero el "redondeo bancario" ya es interesante. "¿Por qué?" - usted pregunta En la subred, a menudo utilizamos la clase Convert , que proporciona muchos métodos para convertir un tipo de datos a otro (que no debe confundirse con la conversión, se describirá a continuación). Y ahora, resulta que al convertir números de punto flotante ( doble, flotante, decimal ) en un tipo entero de tipo int mediante el método Convert.ToInt32, el redondeo "bancario" funciona bajo el capó. ¡Se usa aquí por defecto!

Y parece que la ignorancia de esta bagatela no afecta en gran medida su trabajo, pero tan pronto como tenga que trabajar con estadísticas e indicadores de cálculo basados ​​en un montón de todo tipo de registros y números, esto saldrá de lado. Porque esperamos (por ignorancia) que todas nuestras conversiones / redondeos en los cálculos funcionarán de acuerdo con las reglas de redondeo "matemático". Y parecemos un carnero en una nueva puerta por el resultado del redondeo 6.5 , que es 6 .

El primer pensamiento del programador que ve esto es: "¿Quizás el redondeo funciona en la dirección opuesta, y de acuerdo con las reglas, se redondea al número más pequeño?", "¿Quizás olvidé algo de las matemáticas escolares?". Luego va a google y entiende que no han olvidado nada, y que está ocurriendo algún tipo de mafia. En este paso, el desarrollador perezoso decidirá que este es el comportamiento estándar del método Convert.ToInt32 , redondeará al entero más pequeño y puntuará para una búsqueda adicional. Y pensará que si Convert.ToInt32 (6,5) = 6 , entonces por analogía Convert.ToInt32 (7,5) = 7 . Pero ahí estaba. En el futuro, dichos desarrolladores serán golpeados en la cabeza con un montón de errores del departamento de control de calidad.

El hecho es que el redondeo "bancario" funciona un poco más complicado: redondea un número al entero par más cercano, y no al módulo entero más cercano. Este tipo de redondeo es supuestamente más honesto en caso de aplicación en operaciones bancarias: los bancos no se privarán a sí mismos ni a sus clientes, suponiendo que hay tantas operaciones con una parte entera igual como operaciones con una parte entera impar. Pero en cuanto a mí, todavía no está claro :) Entonces, por eso Convert.ToInt32 (6.5) dará un resultado de 6 , y el resultado para Convert.ToInt32 (7.5) será 8 , no 7 :)

¿Qué hacer para que todos conozcan el redondeo "matemático" familiar? Los métodos de conversión de clases no tienen opciones de redondeo adicionales. Es cierto, porque esta clase sirve principalmente no para redondeo, sino para conversión de tipo. La maravillosa clase de matemáticas con su método redondo viene al rescate. Pero aquí también tenga cuidado, porque de forma predeterminada este método funciona igual que redondear en Convert.ToInt32 () , de acuerdo con la regla de "banca". Sin embargo, este comportamiento se puede cambiar utilizando el segundo argumento, que es parte del método Round . Entonces, Math.Round (someNumber, MidpointRounding.ToEven ) nos dará el redondeo "bancario" predeterminado. Pero Math.Round (someNumber, MidpointRounding.AwayFromZero ) funcionará de acuerdo con las reglas habituales de redondeo "matemático".

Y, por cierto, Convert.ToInt32 () no usa System.Math.Round () debajo del capó. Especialmente desenterrando la implementación de este método en github, el redondeo se considera de acuerdo con los residuos:

 public static int ToInt32(double value) { if (value >= 0) { if (value < 2147483647.5) { int result = (int)value; double dif = value - result; if (dif > 0.5 || dif == 0.5 && (result & 1) != 0) result++; return result; } } else { if (value >= -2147483648.5) { int result = (int)value; double dif = value - result; if (dif < -0.5 || dif == -0.5 && (result & 1) != 0) result--; return result; } } throw new OverflowException(Environment.GetResourceString("Overflow_Int32")); } 

Y, por último, algunas palabras sobre la conversión de tipos :

 var number = 6.9; var intNumber = (int)number; 

En este ejemplo, lanzo un tipo de coma flotante ( doble en este caso) a un entero int . Entonces, cuando se convierte a tipos enteros, toda la parte no entera simplemente se trunca . En consecuencia, en este ejemplo, la variable " intNumber " contendrá el número 6 . Aquí no hay reglas de redondeo, solo corta todo lo que viene después del punto decimal. Recuerda esto!

Enlaces relacionados:


PD: Gracias a Maxim Yakushkin por llamar la atención sobre este momento implícito.

PPS Por cierto, en python, el redondeo por defecto funciona de la misma manera sobre una base "bancaria". Quizás lo mismo esté en tu idioma, ten cuidado con los números :)

Source: https://habr.com/ru/post/462299/


All Articles