2013-02-13 27 views
6

Tôi viết một vòng lặp đếm tăng lên với một phao, nhưng tôi đã đi qua một vấn đề số học dấu chấm động được minh họa trong ví dụ sau:C++ Làm thế nào để tránh dấu chấm động số học lỗi

for(float value = -2.0; value <= 2.0; value += 0.2) 
    std::cout << value << std::endl; 

Đây là đầu ra:

-2 
-1.8 
-1.6 
-1.4 
-1.2 
-1 
-0.8 
-0.6 
-0.4 
-0.2 
1.46031e-07 
0.2 
0.4 
0.6 
0.8 
1 
1.2 
1.4 
1.6 
1.8 

Tại sao tôi nhận được 1.46031e-07 thay vì 0? Tôi biết điều này có liên quan đến các lỗi dấu phẩy động, nhưng tôi không thể hiểu tại sao nó xảy ra và tôi nên làm gì để ngăn chặn điều này xảy ra (nếu có cách nào). Ai đó có thể giải thích (hoặc chỉ cho tôi một liên kết) sẽ giúp tôi hiểu? Bất kỳ đầu vào nào được đánh giá cao. Cảm ơn!

+4

"Làm thế nào để tránh dấu chấm động lỗi số học" - bạn có thể không, xin lỗi. –

+0

điều này đã được yêu cầu và trả lời nhiều lần – bernie

+3

'0.2' không thể được biểu diễn chính xác bằng' float' (giả sử số học dấu chấm động IEEE754). Bạn có thể thấy điều này nếu bạn tăng độ chính xác của đầu ra: [example] (http://liveworkspace.org/code/3ZXIxx$0). – Mankarse

Trả lời

7

Điều này là do số dấu phẩy động chỉ có độ chính xác riêng biệt nhất định.

0,2 không thực sự là 0,2, nhưng được biểu thị nội bộ dưới dạng một số hơi khác.

Đó là lý do tại sao bạn thấy sự khác biệt.

Điều này là phổ biến trong tất cả các tính toán điểm nổi và bạn thực sự không thể tránh được.

+1

Điều quan trọng là chỉ ra rằng mặc dù 0,2 không thể được biểu diễn chính xác dưới dạng float, -2.0 và 2.0 có thể. Tôi chỉ ra điều này để tránh ấn tượng rằng toán học dấu phẩy động là tùy ý và thất thường. Tất cả những gì đang xảy ra là cơ sở sử dụng phao và kép 2 và 0,2 tương đương với 1/5, không thể được biểu diễn dưới dạng số cơ số 2 hữu hạn. -2, 2.0, 0.5, 0.25, -.375 và 178432 có thể được biểu diễn chính xác. –

1

Tìm hiểu về biểu diễn dấu phẩy động với một số sách Thuật toán hoặc sử dụng internet. Có rất nhiều tài nguyên ngoài kia.

Vì thời gian, những gì bạn muốn dường như là một số cách để có được số không khi một cái gì đó rất gần với số không. và tất cả chúng ta đều biết rằng chúng ta gọi quá trình này là "làm tròn". :) vậy tại sao bạn không sử dụng nó trong khi in những con số đó. Chức năng printf cung cấp khả năng định dạng tốt cho những thứ này. kiểm tra các bảng trong liên kết sau nếu bạn không biết cách định dạng bằng printf. (Bạn có thể sử dụng các định dạng để làm tròn và hiển thị những con số chính xác) printf ref: http://www.cplusplus.com/reference/cstdio/printf/?kw=printf

- chỉnh sửa -

có lẽ một số bạn biết biết rằng theo toán học 1,99999999 .... là như nhau là 2.0. Chỉ khác biệt là biểu diễn. Nhưng con số là như nhau.

vấn đề điểm nổi của bạn là chút bit tương tự như vậy. (Đây là chỉ dành riêng cho chỉ rõ bạn vấn đề của bạn không phải là giống như 1,9999 .... điều..)

5

Sử dụng số nguyên và phân chia xuống:

for(int value = -20; value <= 20; value += 2) 
    std::cout << (value/10.0) << std::endl; 
+0

+1, nhưng ... bằng cách chia 'value' bởi' 10.0', bạn đang gợi ý trình biên dịch cần tính toán với độ chính xác gấp đôi, và sau đó chuyển sang độ chính xác đơn (chương trình OP mà bạn đang cố gắng mô phỏng có một biến chính xác đơn). Nó sẽ xảy ra rằng điều này cho kết quả tương tự như một bộ phận chính xác đơn thẳng. Nhưng, vì lý do tại sao nó cho kết quả giống nhau là không tầm thường, trình biên dịch gần như chắc chắn sẽ tạo ra mã cho một bộ phận chính xác gấp đôi theo sau là chuyển đổi từ đôi sang độ chính xác đơn. Vì lý do này, 'giá trị/10.0f' sẽ tốt hơn một chút. –

+0

Tôi vừa kiểm tra và GCC tạo ra một bộ phận chính xác đơn cho 'float r = f/10.0;'. Tôi rất ấn tượng (và bình luận trước đó của tôi mất nhiều giá trị của nó). –

23

Như mọi người khác đã nói, đây là thực tế là các số thực là một tập vô hạn và không đếm được, trong khi các biểu diễn điểm động sử dụng một số bit hữu hạn. Số dấu chấm động chỉ có thể ước tính số thực và thậm chí trong nhiều trường hợp đơn giản là không chính xác, do định nghĩa của chúng. Như bạn đã thấy, 0.2 không thực sự là 0.2 nhưng thay vào đó là một con số rất gần với nó. Khi bạn thêm các số này vào value, bạn tích lũy lỗi ở mỗi bước.

Là một thay thế, hãy thử sử dụng int s cho lặp của bạn và chia kết quả để có được nó trở lại trong lĩnh vực bạn yêu cầu:

for (int value = -20; value <= 20; value += 2) { 
    std::cout << (value/10.f) << std::endl; 
} 

Đối với tôi điều này mang lại:

-2 
-1.8 
-1.6 
-1.4 
-1.2 
-1 
-0.8 
-0.6 
-0.4 
-0.2 
0 
0.2 
0.4 
0.6 
0.8 
1 
1.2 
1.4 
1.6 
1.8 
2 
+2

Tôi không thể tin rằng đây không phải là câu trả lời được chấp nhận, nhìn thấy như thế này và 1 câu trả lời khác là những người duy nhất cung cấp một giải pháp. – Celeritas

+0

Nó nhắc tôi những vấn đề nổi tiếng liên quan đến [ULPs] (http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition) –

5

Hãy làm vòng lặp của bạn, nhưng với độ chính xác đầu ra tăng lên.

mã:

for(float value = -2.0; value <= 2.0; value += 0.2) 
    std::cout << std::setprecision(100) << value << std::endl; 

đầu ra:

-2 
-1.7999999523162841796875 
-1.599999904632568359375 
-1.3999998569488525390625 
-1.19999980926513671875 
-0.999999821186065673828125 
-0.79999983310699462890625 
-0.599999845027923583984375 
-0.3999998569488525390625 
-0.19999985396862030029296875 
1.460313825418779742904007434844970703125e-07 
0.20000015199184417724609375 
0.400000154972076416015625 
0.6000001430511474609375 
0.800000131130218505859375 
1.00000011920928955078125 
1.20000016689300537109375 
1.40000021457672119140625 
1.60000026226043701171875 
1.80000030994415283203125 
Các vấn đề liên quan