Arredondando para o todo no .NET

Tudo ku barbudo , camaradas!

Todos sabemos o que é arredondamento. Se alguém esqueceu, o arredondamento é a substituição de um número pelo seu valor aproximado, escrito com menos dígitos significativos. Se você perguntar a uma pessoa em tempo real o que acontece quando você arredonda 6,5 ​​para números inteiros, ela responderá “7” sem hesitar. A escola nos ensinou que os números são arredondados para o número inteiro mais próximo, que é maior em valor absoluto. Ou seja, se no número arredondado a parte fracionária for igual ou mais da metade da descarga da parte inteira, arredondaremos o número original para o número maior mais próximo.

Simplificando:
6,4 = 6 6,5 = 7 6,6 = 7 
etc.

E assim, saindo da escola e se tornando programadores, geralmente esperamos o mesmo comportamento de nossas poderosas linguagens de programação. Esquecendo completamente que na escola aprendemos "arredondamentos matemáticos", mas, de fato, existem muito mais tipos de arredondamentos. Somente na Wikipedia, você pode cavar quantas opções de arredondamento são 0,5 para o número inteiro mais próximo:

  • Arredondamento matemático
  • Arredondamento aleatório
  • Arredondamento alternado
  • Arredondamento bancário

O primeiro tipo, "arredondamento matemático", todos aprendemos na escola. Você pode ler sobre o segundo e o terceiro tipos à sua vontade, eles não são interessantes para mim neste artigo hoje.

Mas o "arredondamento bancário" já é interessante. "Porque?" - você pergunta. Na sub-rede, geralmente usamos a classe Convert , que fornece muitos métodos para converter um tipo de dados em outro (para não confundir com a conversão, ela será descrita abaixo). E agora, ao converter números de ponto flutuante ( double, float, decimal ) em um número inteiro tipo int pelo método Convert.ToInt32, o arredondamento "bancário" funciona sob o capô. É usado aqui por padrão!

E parece que a ignorância dessa ninharia não afeta muito o seu trabalho, mas assim que você tiver que trabalhar com estatísticas e indicadores de cálculo com base em vários tipos de registros e números, isso acontecerá de lado. Porque esperamos (por ignorância) que todas as nossas conversões / arredondamentos nos cálculos funcionem de acordo com as regras do arredondamento "matemático". E parecemos um aríete em um novo portão para o resultado do arredondamento 6.5 , que é 6 .

O primeiro pensamento do programador que vê isso é: “Talvez o arredondamento funcione na direção oposta e, pelas regras que arredondar para o menor número?”, “Talvez eu tenha esquecido algo da matemática da escola?” Então ele vai ao google e entende que eles não esqueceram nada, e que algum tipo de multidão está acontecendo. Nesta etapa, o desenvolvedor lento decidirá que esse é o comportamento padrão do método Convert.ToInt32 , arredondar para o menor número inteiro e pontuar para pesquisas adicionais. E ele pensará que, se Convert.ToInt32 (6,5) = 6 , por analogia Convert.ToInt32 (7,5) = 7 . Mas lá estava. No futuro, esses desenvolvedores receberão muitos bugs do departamento de controle de qualidade.

O fato é que o arredondamento "bancário" funciona um pouco mais complicado - arredonda um número para o número inteiro par mais próximo e não para o módulo inteiro mais próximo. Esse tipo de arredondamento é supostamente mais honesto no caso de aplicação em operações bancárias - os bancos não privam a si mesmos ou a clientes, supondo que existam tantas operações com uma parte inteira par quanto operações com uma parte inteira ímpar. Mas quanto a mim - ainda não está claro :) É por isso que Convert.ToInt32 (6.5) fornecerá um resultado de 6 , e o resultado para Convert.ToInt32 (7.5) será 8 , e não 7 :)

O que fazer para familiarizar todos os arredondamentos "matemáticos"? Os métodos da classe Convert não têm opções de arredondamento adicionais. É verdade, porque essa classe serve principalmente não para arredondamentos, mas para conversão de tipos. A maravilhosa aula de matemática com seu método Round vem em socorro. Mas aqui também tenha cuidado, porque, por padrão, esse método funciona da mesma forma que o arredondamento em Convert.ToInt32 () - de acordo com a regra "banking". No entanto, esse comportamento pode ser alterado usando o segundo argumento, que faz parte do método Round . Portanto, Math.Round (someNumber, MidpointRounding.ToEven ) fornecerá o arredondamento "bancário" padrão. Mas Math.Round (someNumber, MidpointRounding.AwayFromZero ) funcionará de acordo com as regras usuais do arredondamento "matemático".

E, a propósito, Convert.ToInt32 () não usa System.Math.Round () sob o capô. Desenterrar especialmente a implementação desse método no arredondamento do github é considerado de acordo com os resíduos:

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

E, finalmente, algumas palavras sobre a conversão de tipos :

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

Neste exemplo, converti um tipo de ponto flutuante ( duplo neste caso) para um número inteiro int . Portanto, ao converter para tipos inteiros, toda a parte não inteira é simplesmente truncada . Por conseguinte, neste exemplo, a variável " intNumber " conterá o número 6 . Não há regras de arredondamento aqui, apenas cortando tudo o que vem após o ponto decimal. Lembre-se disso!

Links relacionados:


PS Agradecemos a Maxim Yakushkin por chamar a atenção para esse momento implícito.

PPS Aliás, em python, o arredondamento por padrão funciona da mesma maneira em uma base "bancária". Talvez a mesma coisa esteja no seu idioma, tenha cuidado com os números :)

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


All Articles