Tại sao Math.Round (2.5) trả về 2 thay vì 3?


415

Trong C #, kết quả Math.Round(2.5)là 2.

Nó được cho là 3, phải không? Tại sao nó là 2 thay vì trong C #?


5
Đó thực sự là một tính năng. Xem <a href=" msdn.microsoft.com/en-us/library/... MSDN documentation</a> . Kiểu này làm tròn được gọi là làm tròn của ngân hàng. Đối với một workaround, có <a href =" MSDN. microsoft.com/en-us/l Library / Quá tải </a> cho phép người gọi chỉ định cách thực hiện làm tròn số.
Joe

1
Rõ ràng phương thức làm tròn, khi được yêu cầu làm tròn một số chính xác giữa hai số nguyên, trả về số nguyên chẵn. Vì vậy, Math.Round (3.5) trả về 4. Xem bài viết này
Matthew Jones

20
Math.Round(2.5, 0, MidpointRounding.AwayFromZero);
Robert Durgin

SQL Server làm tròn theo cách đó; kết quả kiểm tra thú vị khi có một kiểm tra đơn vị kiểm tra đơn vị C # được thực hiện trong T-SQL.
idstam

7
@amed đó không phải là một lỗi. Đó là cách các điểm nổi nhị phân hoạt động. 1.005không thể được đại diện chính xác trong gấp đôi. Có lẽ là vậy 1.00499.... Nếu bạn sử dụng Decimalvấn đề này sẽ biến mất. Sự tồn tại của quá tải Math.Round có số chữ số thập phân tăng gấp đôi là một lựa chọn thiết kế đáng ngờ IMO, vì nó sẽ hiếm khi hoạt động theo cách có ý nghĩa.
CodeInChaos

Câu trả lời:


560

Đầu tiên, dù sao đây cũng không phải là lỗi C # - đó sẽ là lỗi .NET. C # là ngôn ngữ - nó không quyết định cách thức Math.Roundtriển khai.

Và thứ hai, không - nếu bạn đọc tài liệu , bạn sẽ thấy rằng làm tròn mặc định là "làm tròn đến chẵn" (làm tròn số của ngân hàng):


Kiểu giá trị trả về : System.Double
Số nguyên gần nhất a. Nếu thành phần phân số của a là một nửa giữa hai số nguyên, một trong số đó là số chẵn và số lẻ khác, thì số chẵn được trả về. Lưu ý rằng phương thức này trả về một Doublethay vì một loại tích phân.

Bình luận
Hành vi của phương pháp này sau IEEE tiêu chuẩn 754, phần 4. Đây là loại tròn đôi khi được gọi là làm tròn đến gần nhất, hoặc làm tròn của ngân hàng. Nó giảm thiểu các lỗi làm tròn xuất phát từ việc làm tròn một giá trị trung điểm theo một hướng duy nhất.

Bạn có thể chỉ định cách làm Math.Roundtròn điểm giữa bằng cách sử dụng quá tải cần một MidpointRoundinggiá trị. Có một tình trạng quá tải MidpointRoundingtương ứng với từng tình trạng quá tải không có:

Cho dù mặc định này đã được lựa chọn tốt hay không là một vấn đề khác. ( MidpointRoundingchỉ được giới thiệu trong .NET 2.0. Trước đó tôi không chắc có cách dễ dàng nào để thực hiện hành vi mong muốn mà không tự mình thực hiện.) Đặc biệt, lịch sử đã chỉ ra rằng đó không phải là hành vi được mong đợi - và trong hầu hết các trường hợp đó là một tội lỗi chính trong thiết kế API. Tôi có thể thấy lý do tại sao Rounding của Banker hữu ích ... nhưng nó vẫn gây ngạc nhiên cho nhiều người.

Bạn có thể quan tâm để xem enum tương đương Java ( RoundingMode) cung cấp nhiều tùy chọn hơn nữa. (Nó không chỉ đối phó với điểm giữa.)


4
Tôi không biết đây có phải là một lỗi không, tôi nghĩ rằng đó là do thiết kế vì .5 gần với số nguyên thấp nhất gần nhất với số nguyên cao nhất gần nhất.
Stan R.

3
Tôi nhớ hành vi này trong VB trước khi .NET được áp dụng.
John Fiala

7
Thật vậy, Tiêu chuẩn IEEE 754, phần 4 là tài liệu nêu.
Jon Skeet

2
Tôi đã bị đốt cháy bởi điều này một thời gian trước đây và nghĩ rằng đó cũng là sự mất trí tuyệt đối. May mắn thay, họ đã thêm một cách để xác định làm tròn mà tất cả chúng ta đã học ở trường tiểu học; MidpointRounding.
Shea

26
+1 cho "đó không phải là hành vi được mong đợi [...] đó là một tội lỗi chính trong thiết kế API"
BlueRaja - Danny Pflughoeft

215

Điều đó được gọi là làm tròn đến chẵn (hoặc làm tròn số của ngân hàng), đây là một chiến lược làm tròn hợp lệ để giảm thiểu các lỗi tích lũy trong các khoản tiền (MidpointRounding.ToEven). Lý thuyết là, nếu bạn luôn làm tròn một số 0,5 theo cùng một hướng, các lỗi sẽ tích lũy nhanh hơn (làm tròn đến chẵn được cho là để giảm thiểu điều đó) (a) .

Thực hiện theo các liên kết này cho các mô tả MSDN về:

  • Math.Floor, mà làm tròn xuống về phía vô cực tiêu cực.
  • Math.Ceiling, mà làm tròn lên về phía vô cực tích cực.
  • Math.Truncate, làm tròn lên hoặc xuống về không.
  • Math.Round, làm tròn đến số nguyên gần nhất hoặc số vị trí thập phân được chỉ định. Bạn có thể chỉ định hành vi nếu nó chính xác tương đương giữa hai khả năng, chẳng hạn như làm tròn sao cho chữ số cuối cùng là chẵn (" Round(2.5,MidpointRounding.ToEven)" trở thành 2) hoặc sao cho nó cách xa 0 (" Round(2.5,MidpointRounding.AwayFromZero)" trở thành 3).

Sơ đồ và bảng sau đây có thể giúp:

-3        -2        -1         0         1         2         3
 +--|------+---------+----|----+--|------+----|----+-------|-+
    a                     b       c           d            e

                       a=-2.7  b=-0.5  c=0.3  d=1.5  e=2.8
                       ======  ======  =====  =====  =====
Floor                    -3      -1      0      1      2
Ceiling                  -2       0      1      2      3
Truncate                 -2       0      0      1      2
Round(ToEven)            -3       0      0      2      3
Round(AwayFromZero)      -3      -1      0      2      3

Lưu ý rằng Roundnó mạnh hơn rất nhiều so với vẻ ngoài của nó, đơn giản vì nó có thể làm tròn đến một số vị trí thập phân cụ thể. Tất cả những người khác làm tròn đến số thập phân không. Ví dụ:

n = 3.145;
a = System.Math.Round (n, 2, MidpointRounding.ToEven);       // 3.14
b = System.Math.Round (n, 2, MidpointRounding.AwayFromZero); // 3.15

Với các chức năng khác, bạn phải sử dụng thủ thuật nhân / chia để đạt được hiệu quả tương tự:

c = System.Math.Truncate (n * 100) / 100;                    // 3.14
d = System.Math.Ceiling (n * 100) / 100;                     // 3.15

(a) Tất nhiên, lý thuyết đó phụ thuộc vào thực tế là dữ liệu của bạn có sự phân tán các giá trị khá đồng đều trên các nửa chẵn (0,5, 2,5, 4,5, ...) và các nửa lẻ (1.5, 3.5, ...).

Nếu tất cả "nửa giá trị" là evens (ví dụ), các lỗi sẽ tích lũy nhanh như thể bạn luôn làm tròn.


3
Còn được gọi là Làm tròn Ngân hàng
Pondidum

Lời giải thích hay! Tôi muốn tự mình xem lỗi tích lũy như thế nào và tôi đã viết một tập lệnh cho thấy các giá trị được làm tròn bằng cách sử dụng làm tròn của ngân hàng, về lâu dài, có tổng và trung bình gần hơn với các giá trị ban đầu này. github.com/AADEusW/RoundingDemo (hình ảnh các lô có sẵn)
Amadeusz Wieczorek

Chỉ một thời gian ngắn sau: không nên eđánh dấu (= 2.8) đúng hơn 2đánh dấu?
superjos

Một cách đơn giản để ghi nhớ và giả sử vị trí thứ mười là 5: - vị trí và vị trí thứ mười đều lẻ = làm tròn lên - vị trí và vị trí thứ mười được trộn = làm tròn xuống * Không phải là số lẻ * Đảo ngược cho số âm
Arkham Angel

@ArkhamAngel, điều đó thực sự có vẻ khó nhớ hơn là chỉ "tạo chữ số cuối cùng chẵn" :-)
paxdiablo

42

Từ MSDN, Math.Round (nhân đôi a) trả về:

Số nguyên gần nhất a. Nếu thành phần phân số của a là một nửa giữa hai số nguyên, một trong số đó là số chẵn và số lẻ khác, thì số chẵn được trả về.

... Và vì vậy, 2,5, nằm giữa 2 và 3, được làm tròn xuống số chẵn (2). đây được gọi là Làm tròn Ngân hàng (hoặc làm tròn số chẵn) và là một tiêu chuẩn làm tròn thường được sử dụng.

Bài viết MSDN tương tự:

Hành vi của phương pháp này tuân theo Tiêu chuẩn IEEE 754, phần 4. Loại làm tròn này đôi khi được gọi là làm tròn đến gần nhất hoặc làm tròn số của ngân hàng. Nó giảm thiểu các lỗi làm tròn xuất phát từ việc làm tròn một giá trị trung điểm theo một hướng duy nhất.

Bạn có thể chỉ định một hành vi làm tròn khác bằng cách gọi quá tải Math.Round có MidpointRoundingchế độ.


37

Bạn nên kiểm tra MSDN cho Math.Round:

Hành vi của phương pháp này tuân theo Tiêu chuẩn IEEE 754, phần 4. Loại làm tròn này đôi khi được gọi là làm tròn đến gần nhất hoặc làm tròn số của ngân hàng.

Bạn có thể chỉ định hành vi Math.Roundsử dụng quá tải:

Math.Round(2.5, 0, MidpointRounding.AwayFromZero); // gives 3

Math.Round(2.5, 0, MidpointRounding.ToEven); // gives 2

31

Bản chất của làm tròn

Hãy xem xét nhiệm vụ làm tròn một số có chứa một phân số thành một số nguyên. Quá trình làm tròn trong trường hợp này là để xác định toàn bộ số nào thể hiện đúng nhất số bạn đang làm tròn.

Nói chung, hoặc làm tròn 'số học', rõ ràng là 2.1, 2.2, 2.3 và 2.4 làm tròn thành 2.0; và 2.6, 2.7, 2.8 và 2.9 đến 3.0.

Đó là 2.5, không gần 2.0 so với 3.0. Tùy bạn chọn giữa 2.0 và 3.0, sẽ có giá trị như nhau.

Đối với các số trừ, -2.1, -2.2, -2.3 và -2.4, sẽ trở thành -2.0; và -2,6, 2.7, 2.8 và 2.9 sẽ trở thành -3.0 theo cách làm tròn số học.

Đối với -2,5, một sự lựa chọn là cần thiết giữa -2.0 và -3.0.

Các hình thức làm tròn khác

'Làm tròn' lấy bất kỳ số nào có số thập phân và biến nó thành số 'toàn bộ' tiếp theo. Do đó, không chỉ làm 2.5 và 2.6 làm tròn thành 3.0, mà cả 2.1 và 2.2 cũng vậy.

Làm tròn di chuyển cả số dương và số âm đi từ số không. Ví dụ. 2.5 đến 3.0 và -2.5 đến -3.0.

'Làm tròn số' cắt bớt số bằng cách cắt bớt các chữ số không mong muốn. Điều này có tác dụng di chuyển số về không. Ví dụ. 2,5 đến 2,0 và -2,5 đến -2,0

Trong "làm tròn ngân hàng" - ở dạng phổ biến nhất - 0,5 được làm tròn được làm tròn lên hoặc xuống để kết quả làm tròn luôn luôn là một số chẵn. Do đó, 2,5 vòng thành 2.0, 3.5 đến 4.0, 4.5 đến 4.0, 5.5 đến 6.0, v.v.

'Làm tròn luân phiên' xen kẽ quá trình cho bất kỳ 0,5 điểm nào giữa làm tròn xuống và làm tròn lên.

'Làm tròn ngẫu nhiên' làm tròn 0,5 lên hoặc xuống trên cơ sở hoàn toàn ngẫu nhiên.

Đối xứng và bất đối xứng

Hàm làm tròn được gọi là 'đối xứng' nếu nó làm tròn tất cả các số từ 0 hoặc làm tròn tất cả các số về 0.

Hàm là 'không đối xứng' nếu làm tròn các số dương về 0 và các số âm cách xa 0 .. Ví dụ. 2,5 đến 2,0; và -2,5 đến -3,3.

Ngoài ra, bất đối xứng là một hàm làm tròn số dương từ số 0 và số âm thành số không. Ví dụ. 2,5 đến 3,0; và -2,5 đến -2,0.

Hầu hết mọi người nghĩ về làm tròn đối xứng, trong đó -2,5 sẽ được làm tròn theo hướng -3.0 và 3.5 sẽ được làm tròn theo 4.0. (bằng C #Round(AwayFromZero))


28

Mặc định MidpointRounding.ToEven, hoặc làm tròn số của Ngân hàng ( 2,5 trở thành 2, 4,5 trở thành 4 và v.v. ) đã khiến tôi bực mình trước khi viết báo cáo cho kế toán, vì vậy tôi sẽ viết một vài từ về những gì tôi đã tìm ra, trước đây và từ việc tìm hiểu về nó bài này.

Những nhân viên ngân hàng này đang làm tròn số chẵn (có lẽ là chủ ngân hàng Anh!)?

Từ wikipedia

Nguồn gốc của thuật ngữ làm tròn ngân hàng vẫn còn mơ hồ hơn. Nếu phương pháp làm tròn này từng là một tiêu chuẩn trong ngân hàng, bằng chứng đã chứng minh là cực kỳ khó tìm. Ngược lại, phần 2 của báo cáo của Ủy ban châu Âu Giới thiệu về đồng Euro và làm tròn số lượng tiền tệ cho thấy rằng trước đây không có cách tiếp cận tiêu chuẩn nào đối với làm tròn trong ngân hàng; và nó xác định rằng số tiền "nửa đường" nên được làm tròn lên.

Có vẻ như một cách làm tròn rất kỳ lạ đặc biệt đối với ngân hàng, trừ khi tất nhiên các ngân hàng sử dụng để nhận rất nhiều tiền gửi với số tiền chẵn. Gửi 2,4 triệu bảng, nhưng chúng tôi sẽ gọi nó là 2 triệu bảng thưa ông.

Tiêu chuẩn IEEE 754 có từ năm 1985 và đưa ra cả hai cách làm tròn, nhưng với tiêu chuẩn ngân hàng theo khuyến nghị của tiêu chuẩn. Bài viết trên wikipedia này có một danh sách dài về cách các ngôn ngữ thực hiện làm tròn số (sửa tôi nếu bất kỳ điều nào dưới đây là sai) và hầu hết không sử dụng Bankers 'nhưng cách làm tròn bạn được dạy ở trường:

  • Vòng C / C ++ () từ math.h làm tròn từ 0 (không làm tròn số ngân hàng)
  • Java Math.Round làm tròn số 0 (nó tạo ra kết quả, cộng 0,5, chuyển thành một số nguyên). Có một sự thay thế trong BigDecimal
  • Perl sử dụng một cách tương tự như C
  • Javascript giống như Math.Round của Java.

Cảm ơn vì thông tin. Tôi chưa bao giờ nhận ra điều này. Ví dụ của bạn về hàng triệu lời chế giễu một chút, nhưng ngay cả khi bạn làm tròn bằng xu, việc phải trả lãi cho 10 triệu tài khoản ngân hàng sẽ khiến ngân hàng phải trả giá rất nhiều nếu tất cả nửa xu được làm tròn, hoặc sẽ khiến khách hàng phải trả giá rất nhiều nếu tất cả một nửa xu được làm tròn xuống. Vì vậy, tôi có thể tưởng tượng đây là tiêu chuẩn đồng ý. Không chắc chắn nếu điều này thực sự được sử dụng bởi các nhân viên ngân hàng mặc dù. Hầu hết khách hàng sẽ không nhận thấy làm tròn, trong khi mang lại rất nhiều tiền, nhưng tôi có thể tưởng tượng điều này là bắt buộc bởi luật pháp nếu bạn sống ở một quốc gia có luật thân thiện với khách hàng
Harald Coppoolse

15

Từ MSDN:

Theo mặc định, Math.Round sử dụng MidpointRounding.ToEven. Hầu hết mọi người không quen thuộc với "làm tròn đến thậm chí" như là cách thay thế, "làm tròn từ số 0" thường được dạy ở trường hơn. .NET mặc định là "Làm tròn thành chẵn" vì nó vượt trội về mặt thống kê vì nó không chia sẻ xu hướng "làm tròn từ số 0" để làm tròn lên một chút thường xuyên hơn so với làm tròn (giả sử các số được làm tròn có xu hướng dương. )

http://msdn.microsoft.com/en-us/l Library / system.math.round.aspx


3

Vì Silverlight không hỗ trợ tùy chọn MidpointRounding, bạn phải tự viết. Cái gì đó như:

public double RoundCorrect(double d, int decimals)
{
    double multiplier = Math.Pow(10, decimals);

    if (d < 0)
        multiplier *= -1;

    return Math.Floor((d * multiplier) + 0.5) / multiplier;

}

Để biết các ví dụ bao gồm cách sử dụng phần này như một phần mở rộng, hãy xem bài đăng: .NET và Silverlight Rounding


3

Tôi gặp vấn đề này khi máy chủ SQL của tôi làm tròn 0,5 đến 1 trong khi ứng dụng C # của tôi thì không. Vì vậy, bạn sẽ thấy hai kết quả khác nhau.

Đây là một triển khai với int / long. Đây là cách Java làm tròn.

int roundedNumber = (int)Math.Floor(d + 0.5);

Đây có lẽ là phương pháp hiệu quả nhất mà bạn có thể nghĩ ra.

Nếu bạn muốn giữ nó gấp đôi và sử dụng độ chính xác thập phân, thì đó thực sự chỉ là vấn đề sử dụng số mũ 10 dựa trên số lượng thập phân.

public double getRounding(double number, int decimalPoints)
{
    double decimalPowerOfTen = Math.Pow(10, decimalPoints);
    return Math.Floor(number * decimalPowerOfTen + 0.5)/ decimalPowerOfTen;
}

Bạn có thể nhập một số thập phân âm cho các điểm thập phân và nó cũng tốt.

getRounding(239, -2) = 200


0

Bài đăng này có câu trả lời bạn đang tìm kiếm:

http://weblogs.asp.net/sfurman/archive/2003/03/07/13737.aspx

Về cơ bản đây là những gì nó nói:

Giá trị trả lại

Số giá trị gần nhất với độ chính xác bằng chữ số. Nếu giá trị nằm giữa hai số, một trong số đó là số chẵn và số lẻ khác, thì số chẵn được trả về. Nếu độ chính xác của giá trị nhỏ hơn chữ số, thì giá trị được trả về không đổi.

Hành vi của phương pháp này tuân theo Tiêu chuẩn IEEE 754, phần 4. Loại làm tròn này đôi khi được gọi là làm tròn đến gần nhất hoặc làm tròn số của ngân hàng. Nếu chữ số bằng 0, loại làm tròn này đôi khi được gọi là làm tròn về số không.


0

Silverlight không hỗ trợ tùy chọn MidpointRounding. Đây là một phương thức mở rộng cho Silverlight có thêm enum MidpointRounding:

public enum MidpointRounding
{
    ToEven,
    AwayFromZero
}

public static class DecimalExtensions
{
    public static decimal Round(this decimal d, MidpointRounding mode)
    {
        return d.Round(0, mode);
    }

    /// <summary>
    /// Rounds using arithmetic (5 rounds up) symmetrical (up is away from zero) rounding
    /// </summary>
    /// <param name="d">A Decimal number to be rounded.</param>
    /// <param name="decimals">The number of significant fractional digits (precision) in the return value.</param>
    /// <returns>The number nearest d with precision equal to decimals. If d is halfway between two numbers, then the nearest whole number away from zero is returned.</returns>
    public static decimal Round(this decimal d, int decimals, MidpointRounding mode)
    {
        if ( mode == MidpointRounding.ToEven )
        {
            return decimal.Round(d, decimals);
        }
        else
        {
            decimal factor = Convert.ToDecimal(Math.Pow(10, decimals));
            int sign = Math.Sign(d);
            return Decimal.Truncate(d * factor + 0.5m * sign) / factor;
        }
    }
}

Nguồn: http://anderly.com/2009/08/08/silverlight-midpoint-rounding-solution/


-1

sử dụng làm tròn tùy chỉnh

public int Round(double value)
{
    double decimalpoints = Math.Abs(value - Math.Floor(value));
    if (decimalpoints > 0.5)
        return (int)Math.Round(value);
    else
        return (int)Math.Floor(value);
}

>.5tạo ra hành vi tương tự như Math.Round. Câu hỏi là whas xảy ra khi phần thập phân là chính xác 0.5. Math.Round cho phép bạn chỉ định loại thuật toán làm tròn mà bạn muốn
Panagiotis Kanavos

-2

Điều này là xấu xí như tất cả địa ngục, nhưng luôn tạo ra làm tròn số học chính xác.

public double ArithRound(double number,int places){

  string numberFormat = "###.";

  numberFormat = numberFormat.PadRight(numberFormat.Length + places, '#');

  return double.Parse(number.ToString(numberFormat));

}

5
Vì vậy, gọi Math.Roundvà chỉ định làm thế nào bạn muốn nó làm tròn.
cấu hình

-2

Đây là cách tôi phải làm việc xung quanh:

Public Function Round(number As Double, dec As Integer) As Double
    Dim decimalPowerOfTen = Math.Pow(10, dec)
    If CInt(number * decimalPowerOfTen) = Math.Round(number * decimalPowerOfTen, 2) Then
        Return Math.Round(number, 2, MidpointRounding.AwayFromZero)
    Else
        Return CInt(number * decimalPowerOfTen + 0.5) / 100
    End If
End Function

Thử với 1,905 với 2 số thập phân sẽ cho 1,91 như mong đợi nhưng Math.Round(1.905,2,MidpointRounding.AwayFromZero) cho 1,90! Phương pháp Math.Round hoàn toàn không nhất quán và không sử dụng được cho hầu hết các vấn đề cơ bản mà các lập trình viên có thể gặp phải. Tôi phải kiểm tra xem (int) 1.905 * decimalPowerOfTen = Math.Round(number * decimalPowerOfTen, 2)vì tôi không muốn làm tròn những gì nên làm tròn xuống.


Math.Round(1.905,2,MidpointRounding.AwayFromZero) trả lại 1.91
Panagiotis Kanavos
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.