Tại sao rất nhiều chương trình (cũ) sử dụng sàn (đầu vào 0,5 +) thay vì đầu vào vòng (đầu vào)?


80

Sự khác biệt nằm ở giá trị trả về đưa ra các đầu vào xung quanh sự ràng buộc mà tôi tin rằng, chẳng hạn như mã này :

int main()
{
    std::cout.precision(100);

    double input = std::nextafter(0.05, 0.0) / 0.1;
    double x1 = floor(0.5 + input);
    double x2 = round(input);

    std::cout << x1 << std::endl;
    std::cout << x2 << std::endl;
}

kết quả đầu ra:

1
0

Nhưng cuối cùng chúng chỉ là những kết quả khác nhau, người ta chọn cái ưa thích của nó. Tôi thấy rất nhiều chương trình C / C ++ "cũ" sử dụng floor(0.5 + input)thay vì round(input).

Có lý do lịch sử nào không? Giá rẻ nhất trên CPU?


20
std :: round di chuyển nửa trường hợp từ 0. Điều đó không đồng nhất về mặt toán học, giống như sàn (f + .5), trong đó các trường hợp nửa chừng luôn hướng về phía trên. Lý do sử dụng phương pháp sàn là nó được yêu cầu để làm tròn thích hợp trong thế giới kỹ thuật.
Michaël Roy

7
Như đã lưu ý trong round () cho float trong C ++ trước C ++ 11, chúng tôi không có round. Như tôi đã lưu ý trong câu trả lời của mình, viết chính xác vòng của bạn là một vấn đề khó.
Shafik Yaghmour

16
@Arne Sử dụng std :: round () khoảng cách giữa các giá trị làm tròn của -0.5 và +0.5 là 2. sử dụng floor, nó là 1. Chỉ xảy ra khi hai giá trị có dấu trái dấu. Rất khó chịu khi cố gắng vẽ các đường thẳng hoặc khiến bạn chọn sai pixel kết cấu.
Michaël Roy

20
Một số ngôn ngữ và môi trường lập trình (bao gồm cả .NET) sử dụng một thứ đánh lừa được gọi là Banker's Rounding, trong đó x.5 làm tròn đến số EVEN gần nhất. Vì vậy, 0,5 làm tròn thành 0 trong khi 1,5 làm tròn thành 2. Bạn có thể tưởng tượng sự nhầm lẫn điều này có thể gây ra khi gỡ lỗi. Tôi nghĩ giải pháp cho 'tính năng' độc ác này là không có hàm .Round () nào cả và thay vào đó là .RoundBankers (), .RoundHalfUp (), .RoundHalfDown (), v.v. (hoặc .BankersRound (), v.v. nhưng intellisense sẽ hoạt động tốt hơn với .RoundBankers ()). Ít nhất bằng cách đó, bạn sẽ buộc phải biết những gì sẽ xảy ra.
user3685427

8
@ user3685427: Banker's Rounding là cần thiết trong các ứng dụng tài chính và thống kê yêu cầu loại bỏ xu hướng đi lên tinh vi và mang tính hệ thống được giới thiệu bằng cách làm tròn số 0 . Hầu như không thể thực hiện nếu không có kiến ​​thức thực tế về việc triển khai dấu phẩy động phần cứng, do đó nó được chọn làm mặc định trong C #.
Pieter Geerkens

Câu trả lời:


115

std::roundđược giới thiệu trong C ++ 11. Trước đó, chỉ std::floorcó sẵn để các lập trình viên sử dụng nó.


Không có gì. Nhưng nó cũ hơn C ++ 11. Tôi nghĩ logic C ++ nhận nó trước 11. Đó là tất cả;)
markzzz

12
@markzzz - Thư viện chuẩn C ++ không tự động kế thừa thư viện chuẩn của C. Việc chọn và chọn đang diễn ra cẩn thận. Chúng đã mất 12 năm để đồng bộ hóa với C99.
StoryTeller - Unslander Monica

2
@haccks: Thật vậy. IMHO C đi trước C ++ về các hàm toán học cho đến C ++ 11.
Bathsheba

3
Điều quan trọng cần lưu ý là phương thức đang được sử dụng ngắt đối với một số đầu vào và cả C ++ 11 dựa trên C99 trong khi C ++ 03 dựa trên C90, loại đi đến điểm @markzzz.
Shafik Yaghmour

1
@markzzz: Nhận xét của bạn được chú ý (bất chấp các nhà phê bình). Vấn đề là, có một khoảng cách dài không có tiêu chuẩn C ++, tiêu chuẩn C ++ đầu tiên là C ++ 98 và bản sửa đổi lớn đầu tiên là C ++ 11. Có một bản cập nhật nhỏ, C ++ 03, nhưng như trang Wikipedia ghi nhận, đó chủ yếu là bản "sửa lỗi". Vì vậy, C ++ 11 là cơ hội đầu tiên để bắt kịp thư viện chuẩn C, sau 13 năm không hoạt động.
Matthieu M.

21

Không có lý do lịch sử nào. Kiểu lệch lạc này đã có từ năm chấm. Dân gian làm điều này khi họ cảm thấy rất, rất nghịch ngợm. Đó là sự lạm dụng của số học dấu phẩy động và nhiều lập trình viên chuyên nghiệp có kinh nghiệm đã mắc phải nó. Ngay cả Java bods cũng đã lên đến phiên bản 1.7. Các chàng trai vui tính.

Phỏng đoán của tôi là một hàm làm tròn tiếng Đức độc đáo không có sẵn chính thức cho đến khi C ++ 11 (mặc dù C nhận được của chúng trong C99), nhưng đó thực sự không có lý do gì để áp dụng cái gọi là thay thế.

Đây là vấn đề: floor(0.5 + input) không phải lúc nào cũng khôi phục kết quả giống như std::roundcuộc gọi tương ứng !

Lý do khá tinh vi: điểm giới hạn cho một phép làm tròn của Đức, a.5đối với một số nguyên a, do một tính chất ngẫu nhiên của vũ trụ, là một hợp lý dyadic . Vì điều này có thể được biểu diễn chính xác trong dấu phẩy động IEEE754 lên đến lũy thừa thứ 52 của 2, và sau đó làm tròn dù sao cũng là không cần lựa chọn, std::roundluôn hoạt động bình thường. Đối với các lược đồ dấu phẩy động khác, hãy tham khảo tài liệu.

Nhưng việc thêm 0.5vào a doublecó thể gây ra sự thiếu chính xác gây ra một số giá trị thấp hơn hoặc quá mức. Nếu bạn nghĩ về nó, việc cộng hai doublegiá trị với nhau - đó là sự khởi đầu của các chuyển đổi denary vô tình - và áp dụng một hàm là một hàm rất mạnh của đầu vào (chẳng hạn như hàm làm tròn), chắc chắn sẽ kết thúc trong nước mắt.

Đừng làm vậy .

Tham khảo: Tại sao Math.round (0.49999999999999994) trả về 1?


2
Nhận xét không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
Andy

2
nearbyint()thường là một lựa chọn tốt hơn round(), vì nearbyintsử dụng chế độ làm tròn hiện tại thay vì tiebreak sôi nổi từ 0 round()(mà x86 thậm chí không có hỗ trợ phần cứng, mặc dù ARM thì có). Cả hai đều được thêm vào C ++ trong C ++ 11.
Peter Cordes

9

Tôi nghĩ rằng đây là nơi bạn sai:

Nhưng cuối cùng chúng chỉ là những kết quả khác nhau, người ta chọn cái ưa thích của nó. Tôi thấy rất nhiều chương trình C / C ++ "cũ" sử dụng sàn (0,5 + đầu vào) thay vì vòng (đầu vào).

Đó không phải là tình huống. Bạn phải chọn sơ đồ làm tròn phù hợp cho miền . Trong một ứng dụng tài chính, bạn sẽ sử dụng các quy tắc của ngân hàng (không sử dụng float). Tuy nhiên, khi lấy mẫu, làm tròn bằng cách sử dụng static_cast<int>(floor(f + .5))tạo ra ít nhiễu lấy mẫu hơn, điều này làm tăng phạm vi động. Khi căn chỉnh pixel, tức là chuyển đổi một vị trí sang tọa độ màn hình, sử dụng bất kỳ phương pháp làm tròn nào khác sẽ tạo ra các lỗ, khoảng trống và các hiện vật khác.


"điều này làm tăng phạm vi động" - trông giống như văn bản thừa vô nghĩa; sao chép-dán từ đâu đó do nhầm lẫn? Có thể muốn xóa nó.
anatolyg

Không. Giảm nhiễu lấy mẫu sẽ giảm tầng nhiễu và điều đó thực sự làm tăng phạm vi động.
Michaël Roy

Bạn có thể cung cấp một ví dụ đơn giản hoặc một tài liệu tham khảo để minh họa việc tăng dải động / giảm nhiễu không?
Ruslan

1
Với cách làm tròn số nguyên tiêu chuẩn (thiếu), tất cả các giá trị từ 0 đến trừ một "biến mất", hay đúng hơn là dấu hiệu thay đổi (cùng giá trị và đầu vào fo 0 thành + 1), tất cả các giá trị âm được bù ít nhất một bit. Đây là một chút nhiễu ở đó với sự biến dạng được thêm vào.
Michaël Roy

4

Một lý do đơn giản có thể là có nhiều phương pháp làm tròn số khác nhau, vì vậy trừ khi bạn biết phương pháp được sử dụng, bạn có thể cho kết quả khác nhau.

Với tầng (), bạn có thể nhất quán với kết quả. Nếu float là .5 hoặc lớn hơn, việc thêm nó sẽ tăng lên giá trị int tiếp theo. Nhưng .49999 sẽ chỉ giảm số thập phân.


+1 Điểm của câu trả lời này được thực hiện bởi chính các nhận xét về câu hỏi, trong đó có sự bất đồng về những gì round()không.
Loduwijk

@Aaron Câu trả lời là sai về những gì floor(x + 0.5)mặc dù.
EOF

Ồ, há! Tốt rồi. Thật là mỉa mai. "Sử dụng X được biết đến nhiều hơn vì chúng tôi không đồng ý hoặc không có kiến ​​thức đầy đủ về Y." Vậy bạn sẽ làm gì khi điều tương tự cũng áp dụng cho X?
Loduwijk

1
@Aaron Dễ dàng. Bạn làm điều lành mạnh và sử dụng duy nhất, sử dụng nearbyint(x)làm tròn số lành mạnh (đến chẵn gần nhất), miễn là bạn không làm rối môi trường dấu phẩy động.
EOF

@EOF: Lựa chọn làm tròn của bạn không tốt. Một hàm làm tròn không có chu kỳ 1 trong thành phần phi tuyến là điên.
Eric Towers

2

Nhiều lập trình viên phỏng theo các thành ngữ mà họ đã học được khi lập trình với các ngôn ngữ khác. Không phải tất cả các ngôn ngữ đều có một round()chức năng và trong những ngôn ngữ đó, việc sử dụng floor(x + 0.5)thay thế là điều bình thường . Khi những lập trình viên này bắt đầu sử dụng C ++, họ không phải lúc nào cũng nhận ra rằng có một phần mềm tích hợp sẵn round(), họ tiếp tục sử dụng phong cách mà họ đã quen.

Nói cách khác, chỉ vì bạn thấy nhiều mã thực hiện điều gì đó, không có nghĩa là có lý do chính đáng để làm điều đó. Bạn có thể tìm thấy các ví dụ về điều này trong mọi ngôn ngữ lập trình. Hãy nhớ định luật Sturgeon :

chín mươi phần trăm của mọi thứ là tào lao

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.