Math.Round 與 MathF.Round 會正確回傳四捨五入的結果

Math.Round(Double, Int32) 以及 MathF.Round(Single, Int32),以及它們 MidpointRounding 的超載,現在會回傳根據輸入 精確值正確 四捨五入到要求的分數位數的值。 部分輸入現在會四捨五入到與先前版本不同(且正確的)結果。 此外,該 digits 論證已不再有上限。

所推出的版本

.NET 11 預覽版 7

以前的行為

先前計算 Math.Round(value, digits, mode)Round(value * 10^digits, mode) / 10^digits。 由於 value * 10^digits 通常無法精確表示,稍低於(或高於)小數中點的數值可能會縮放成精確的中點或跨越四捨五入邊界,結果則是四捨五入錯誤。 大幅度的值在縮放步驟中也可能完全失去其分數位元。

digits論證也限制在 0-15 支持double,0-6 float支持;超出該範圍的值則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

例如, 655.925 儲存為 655.924999999999954525…,位於中點以下 655.925 ,因此正確結果為 655.92

新行為

從 .NET 11 開始,結果會根據輸入的精確值使用任意精度運算計算,回傳的值是最接近正確四捨五入的十進位結果的可表示值。

此外,任何非負 digits 值都被接受;只有負值會拋出 ArgumentOutOfRangeException。 若數字計數達到或超過往返型別所需的精度(17 為 double,9 為 float),則該值保持不變,這才是正確的結果。

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)

破壞性變更的類型

此變更為行為變更

變更原因

先前的結果在很大比例的輸入中錯誤——在支援 digits 範圍內約有5% 的隨機值與正確的四捨五入結果不符。 先前的行為也會拒絕或錯誤處理大量有限輸入。 新實作符合 IEEE 一致性:它會四捨五入輸入的精確值,並回傳最接近且與已產生值 value.ToString("F{digits}") 相符的可表示結果。

0-15和0-6 digits 的上限是舊有按比例10^digits 計時的人工限制。 由於任何數字的實作都正確,限制同時被解除,以避免日後第二次行為失誤。

欲了解更多資訊,請參閱 dotnet/runtime#130574

大多數程式碼不需要修改,且能從修正後的結果中受益。

如果你依賴精確的先驗(錯誤)輸出,則用前述方法明確進行整數,例如。 Math.Round(value * pow10, mode) / pow10 或者,當這些值代表以十為底的數量(如貨幣)時,也可以進行四捨五入 decimal

doublefloat 是二進位浮點數類型,無法精確表示大多數小數分數。 若對十進位數量進行精確的十進位四捨五入,則偏好 Decimal。 <System.Numerics.Decimal32>、 <System.Numerics.Decimal64> 及 <System.Numerics.Decimal128> 是基於 IEEE 754 十進位的類型,具有擴展的範圍與功能,也適合此類工作。

受影響的 API

同樣的修正行為與提升digits範圍流經這些方法的數值介面入口點Half.Rounddouble.Roundfloat.RoundNFloat.Round,例如,、、 、 。 Math.Round(Decimal, Int32) 而單一參數 Math.Round(Double)MathF.Round(Single) 超載則不受影響。