Math.Round och MathF.Round returnerar korrekt avrundade resultat

Math.Round(Double, Int32) och MathF.Round(Single, Int32), och deras MidpointRounding överlagringar, returnerar nu värdet som är korrekt avrundat till det begärda antalet bråksiffror baserat på det exakta värdet för indata. Vissa indata avrundas nu till ett annat (och korrekt) resultat än i tidigare versioner. Dessutom digits har argumentet inte längre en övre gräns.

Version lanserad

.NET 11 Förhandsversion 7

Tidigare beteende

Math.Round(value, digits, mode) Tidigare beräknade .Round(value * 10^digits, mode) / 10^digits Eftersom value * 10^digits det i allmänhet inte är exakt representerande kan värden något under (eller över) en decimal mittpunkt skalas till en exakt mittpunkt eller över en avrundningsgräns, vilket gav ett felaktigt avrundat resultat. Värden med stor storlek kan också förlora sina bråkbitar helt under skalningssteget.

Argumentet digits var också begränsat till 0-15 för double och 0-6 för float; värden utanför det intervallet kastade 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

Till exempel 655.925 lagras som 655.924999999999954525…, vilket är under 655.925 mittpunkten, så rätt resultat är 655.92.

Nytt beteende

Från och med .NET 11 beräknas resultatet från det exakta värdet för indata med hjälp av godtycklig precisionsaritmetik, och det returnerade värdet är det närmaste representerande värdet till det korrekt avrundade decimalresultatet.

Dessutom accepteras alla icke-negativa digits värden. Endast negativa värden genererar ArgumentOutOfRangeException. Siffran räknas vid eller bortom den precision som krävs för att turas om typen (17 för double, 9 för float) lämnar värdet oförändrat, vilket är rätt resultat.

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)

Typ av brytande ändring

Den här ändringen är en beteendeförändring.

Orsak till ändringen

Föregående resultat var felaktiga för en stor del av indata – ungefär 5% av slumpmässiga värden i det intervall som stöds digits skilde sig från det korrekt avrundade resultatet. Det tidigare beteendet avvisade eller misskötte även stora ändliga indata. Den nya implementeringen är IEEE-konsekvent: den avrundar det exakta värdet för indata och returnerar närmaste repeterbara resultat, vilket matchar det värde som value.ToString("F{digits}") redan har producerats.

0-15- och 0-6-landskamperna digits var en artificiell begränsning knuten till den gamla scale-by-metoden10^digits . Eftersom den exakta implementeringen är korrekt för alla antal siffror lyftes taket samtidigt för att undvika en andra beteendepaus senare.

Mer information finns i dotnet/runtime#130574.

De flesta kod behöver ingen ändring och drar nytta av de korrigerade resultaten.

Om du är beroende av exakta tidigare (felaktiga) utdata avrundar du med föregående metod explicit, Math.Round(value * pow10, mode) / pow10till exempel . Du kan också utföra avrundningen med när decimal värdena representerar base-10-kvantiteter, till exempel valuta.

double och float är binära flyttalstyper och kan inte exakt representera de flesta decimaltal. För exakt decimal avrundning av decimalkvantiteter föredrar du Decimal. <System.Numerics.Decimal32>, <System.Numerics.Decimal64> och <System.Numerics.Decimal128> är IEEE 754 decimalbaserade typer med utökade intervall och funktioner och passar även för den här typen av arbete.

Berörda API:er

Samma korrigerade beteende och lyfte digits intervallflöde genom de numeriska gränssnittsinmatningspunkterna som delegerar till dessa metoder, double.Roundtill exempel , float.Round, Half.Roundoch NFloat.Round. Math.Round(Decimal, Int32) och det enskilda argumentet Math.Round(Double) och MathF.Round(Single) överlagringarna påverkas inte.