You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Math.Round(double, int[, MidpointRounding]) and MathF.Round(float, int[, MidpointRounding]) now return the value that is correctly rounded to the requested number of fractional digits based on the exact value of the input, rather than a close approximation produced by scaling the input by a power of 10. As a result, some inputs now round to a different (correct) result than in previous releases.
Additionally, the historical restriction that limited digits to 0-15 for double and 0-6 for float has been removed. Any non-negative digits value is now accepted; only negative values throw ArgumentOutOfRangeException. Digit counts at or beyond the precision needed to round-trip the type (17 for double, 9 for float) leave the value unchanged, which is the correct result.
Version
.NET 11 Preview 7
Previous behavior
Math.Round(value, digits, mode) computed Round(value * 10^digits, mode) / 10^digits. Because value * 10^digits is generally not exactly representable, values that were slightly below (or above) a decimal midpoint could be scaled into an exact midpoint (or across a rounding boundary), producing an incorrectly rounded result. Large magnitudes could also lose their fractional bits entirely during the scaling step. The digits argument was also capped at 0-15 (double) / 0-6 (float), throwing ArgumentOutOfRangeException outside that range.
(655.925 is stored as 655.924999999999954525…, which is below the 655.925 midpoint, so the correct result is 655.92.)
New behavior
The result is computed from the exact value of the input using arbitrary precision arithmetic and is the nearest representable value to the correctly rounded decimal result.
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)
Type of breaking change
Behavioral change: Existing binaries might behave differently at run time.
Reason for change
The previous results were incorrect for a large fraction of inputs (roughly 5% of random values across the supported digits range differed from the correctly rounded result). The behavior also rejected/mis-handled large finite inputs. The new implementation is IEEE-consistent: it rounds the exact value of the input and returns the nearest representable result, matching the value that value.ToString("F{digits}") already produced.
The 0-15 / 0-6digits cap was an artificial limitation tied to the old scale-by-10^digits approach; the exact implementation is correct for any digit count, so the cap has been lifted at the same time to avoid a second behavioral break later.
Recommended action
Most code needs no change and benefits from the corrected results.
If you depend on the exact prior (incorrect) output, round using the previous approach explicitly, e.g. Math.Round(value * pow10, mode) / pow10, or perform the rounding using decimal when the values represent base-10 quantities such as currency.
Reminder: double/float are binary floating-point types and cannot exactly represent most decimal fractions. For exact decimal rounding of decimal quantities, prefer decimal.
The same corrected behavior and lifted digits range flow through the numeric interface entry points that delegate here, e.g. double.Round, float.Round, Half.Round, and NFloat.Round. Math.Round(decimal, ...) and the single-argument Math.Round(double) / MathF.Round(float) overloads are unaffected. Negative digits still throw ArgumentOutOfRangeException.
Description
Math.Round(double, int[, MidpointRounding])andMathF.Round(float, int[, MidpointRounding])now return the value that is correctly rounded to the requested number of fractional digits based on the exact value of the input, rather than a close approximation produced by scaling the input by a power of 10. As a result, some inputs now round to a different (correct) result than in previous releases.Additionally, the historical restriction that limited
digitsto0-15fordoubleand0-6forfloathas been removed. Any non-negativedigitsvalue is now accepted; only negative values throwArgumentOutOfRangeException. Digit counts at or beyond the precision needed to round-trip the type (17 fordouble, 9 forfloat) leave the value unchanged, which is the correct result.Version
.NET 11 Preview 7
Previous behavior
Math.Round(value, digits, mode)computedRound(value * 10^digits, mode) / 10^digits. Becausevalue * 10^digitsis generally not exactly representable, values that were slightly below (or above) a decimal midpoint could be scaled into an exact midpoint (or across a rounding boundary), producing an incorrectly rounded result. Large magnitudes could also lose their fractional bits entirely during the scaling step. Thedigitsargument was also capped at0-15(double) /0-6(float), throwingArgumentOutOfRangeExceptionoutside that range.(
655.925is stored as655.924999999999954525…, which is below the655.925midpoint, so the correct result is655.92.)New behavior
The result is computed from the exact value of the input using arbitrary precision arithmetic and is the nearest representable value to the correctly rounded decimal result.
Type of breaking change
Reason for change
The previous results were incorrect for a large fraction of inputs (roughly 5% of random values across the supported
digitsrange differed from the correctly rounded result). The behavior also rejected/mis-handled large finite inputs. The new implementation is IEEE-consistent: it rounds the exact value of the input and returns the nearest representable result, matching the value thatvalue.ToString("F{digits}")already produced.The
0-15/0-6digitscap was an artificial limitation tied to the old scale-by-10^digitsapproach; the exact implementation is correct for any digit count, so the cap has been lifted at the same time to avoid a second behavioral break later.Recommended action
Most code needs no change and benefits from the corrected results.
Math.Round(value * pow10, mode) / pow10, or perform the rounding usingdecimalwhen the values represent base-10 quantities such as currency.double/floatare binary floating-point types and cannot exactly represent most decimal fractions. For exact decimal rounding of decimal quantities, preferdecimal.Feature area
Core .NET libraries
Affected APIs
System.Math.Round(double, int)System.Math.Round(double, int, System.MidpointRounding)System.MathF.Round(float, int)System.MathF.Round(float, int, System.MidpointRounding)The same corrected behavior and lifted
digitsrange flow through the numeric interface entry points that delegate here, e.g.double.Round,float.Round,Half.Round, andNFloat.Round.Math.Round(decimal, ...)and the single-argumentMath.Round(double)/MathF.Round(float)overloads are unaffected. Negativedigitsstill throwArgumentOutOfRangeException.Implemented by dotnet/runtime#130574; fixes dotnet/runtime#1643.
Note
This issue was drafted by GitHub Copilot on @tannergooding''s behalf.