Math.Round e MathF.Round retornam resultados arredondados corretamente

Math.Round(Double, Int32) e MathF.Round(Single, Int32), e suas MidpointRounding sobrecargas, agora retornam o valor arredondado corretamente para o número solicitado de dígitos fracionários com base no valor exato da entrada. Algumas entradas agora são arredondadas para um resultado diferente (e correto) do que nas versões anteriores. Além disso, o digits argumento não tem mais um limite superior.

Versão introduzida

.NET 11 Versão Prévia 7

Comportamento anterior

Anteriormente, Math.Round(value, digits, mode) computado Round(value * 10^digits, mode) / 10^digits. Como value * 10^digits geralmente não é exatamente representável, valores ligeiramente abaixo (ou acima) de um ponto médio decimais podem ser dimensionados em um ponto médio exato ou em um limite de arredondamento, o que produziu um resultado arredondado incorretamente. Valores com grandes magnitudes também podem perder seus bits fracionários inteiramente durante a etapa de dimensionamento.

O digits argumento também foi limitado a 0-15 para double e 0-6 para float; valores fora desse intervalo jogados ArgumentOutOfRangeException.

Math.Round(655.925, 2, MidpointRounding.AwayFromZero);            // 655.93              (incorrect)
Math.Round(1111111111111111.5, 1, MidpointRounding.AwayFromZero); // 1111111111111111.6  (incorrect)
Math.Round(1.5, 16, MidpointRounding.ToEven);                     // throws ArgumentOutOfRangeException

Por exemplo, 655.925 é armazenado como 655.924999999999954525…, que está abaixo do 655.925 ponto médio, portanto, o resultado correto é 655.92.

Novo comportamento

A partir do .NET 11, o resultado é calculado do valor exato da entrada usando aritmética de precisão arbitrária e o valor retornado é o valor representável mais próximo do resultado decimal arredondado corretamente.

Além disso, qualquer valor não negativo digits é aceito; somente valores negativos são gerados ArgumentOutOfRangeException. Contagens de dígitos em ou além da precisão necessária para viagem de ida e volta do tipo (17 para double, 9 para float) deixam o valor inalterado, que é o resultado correto.

Math.Round(655.925, 2, MidpointRounding.AwayFromZero);            // 655.92              (correct)
Math.Round(1111111111111111.5, 1, MidpointRounding.AwayFromZero); // 1111111111111111.5  (correct)
Math.Round(1.5, 16, MidpointRounding.ToEven);                     // 1.5                 (no longer throws)

Tipo de mudança disruptiva

Esta é uma alteração comportamental.

Motivo da alteração

Os resultados anteriores estavam incorretos para uma grande fração de entradas— cerca de 5% de valores aleatórios no intervalo com digits suporte diferem do resultado arredondado corretamente. O comportamento anterior também rejeitou ou lidou mal com entradas finitas grandes. A nova implementação é consistente com IEEE: arredonda o valor exato da entrada e retorna o resultado representável mais próximo, que corresponde ao valor já value.ToString("F{digits}") produzido.

As tampas 0-15 e 0-6 digits eram uma limitação artificial ligada à velha abordagem scale-by-10^digits . Como a implementação exata está correta para qualquer contagem de dígitos, a tampa foi levantada ao mesmo tempo para evitar uma segunda interrupção comportamental mais tarde.

Para obter mais informações, consulte dotnet/runtime#130574.

A maioria dos códigos não precisa de nenhuma alteração e benefícios dos resultados corrigidos.

Se você depender da saída exata anterior (incorreta), arredondar usando a abordagem anterior explicitamente, por exemplo, Math.Round(value * pow10, mode) / pow10. Como alternativa, execute o arredondamento usando decimal quando os valores representam quantidades base-10, como moeda.

double e float são tipos binários de ponto flutuante e não podem representar exatamente a maioria das frações decimais. Para arredondamento decimais exato de quantidades decimais, prefira Decimal. <System.Numerics.Decimal32>, <System.Numerics.Decimal64> e <System.Numerics.Decimal128> são tipos baseados emcimal IEEE 754 com intervalos e funcionalidades expandidos e também são adequados para esse tipo de trabalho.

APIs afetadas

O mesmo comportamento corrigido e o fluxo de intervalo levantado digits por meio dos pontos de entrada da interface numérica que delegam a esses métodos, por exemplo, double.Round, float.Rounde Half.RoundNFloat.Round. Math.Round(Decimal, Int32) e o argumento Math.Round(Double) único e MathF.Round(Single) as sobrecargas não são afetados.