2013-05-16 21 views
8

Gần đây, tôi đang đọc bài: Double or Nothing from GOTW by Herb Sutter Tôi là một chút nhầm lẫn với các giải thích về chương trình sau đây:Hiểu biết về Guru của Tuần # 67: Đôi hoặc có gì

int main() 
{ 
    double x = 1e8; 
    while(x > 0) 
    { 
     --x; 
    } 
} 

Giả sử rằng mã này chạy 1 giây trong một số máy. Tôi đồng ý với điểm rằng mã như thế này là ngớ ngẩn.

Tuy nhiên, theo giải thích về vấn đề nếu chúng tôi thay đổi x từ float thành double, sau đó trên một số trình biên dịch, nó sẽ giữ cho máy tính chạy mãi mãi. Giải thích được dựa trên báo giá sau đây từ tiêu chuẩn.

Trích dẫn từ phần 3.9.1/8 của chuẩn C++:

Có ba loại dấu chấm động: float, double, và dài gấp đôi. Kiểu double cung cấp độ chính xác ít nhất là float, và kiểu double double cung cấp độ chính xác tối thiểu gấp đôi. Tập hợp các giá trị của kiểu float là một tập con của tập các giá trị của kiểu double; tập hợp các giá trị của kiểu double là một tập hợp con của tập hợp các giá trị của loại dài gấp đôi.

Câu hỏi cho mã là:

bao lâu bạn mong chờ nó để thực hiện nếu bạn thay đổi "đôi" thành "phao"? Tại sao?

Dưới đây là lời giải thích đưa ra:

Nó có lẽ sẽ mất một trong hai khoảng 1 giây (trên một phao nổi thực hiện cụ thể có thể hơi nhanh hơn, càng nhanh, hoặc hơi chậm hơn so với đôi), hoặc mãi mãi , tùy thuộc vào việc float có thể đại diện chính xác tất cả các giá trị nguyên từ 0 đến 1e8 không.

Trích dẫn trên từ tiêu chuẩn có nghĩa là có thể có các giá trị có thể được biểu thị bằng dấu hai chấm nhưng không thể đại diện bằng dấu phẩy. Đặc biệt, trên một số nền tảng và trình biên dịch phổ biến, double có thể biểu diễn chính xác tất cả các giá trị số nguyên trong [0,1e8] nhưng float không thể.

Điều gì xảy ra nếu float không thể đại diện chính xác tất cả các giá trị số nguyên từ 0 đến 1e8? Sau đó, chương trình sửa đổi sẽ bắt đầu đếm ngược, nhưng cuối cùng sẽ đạt đến giá trị N không thể được biểu diễn và N-1 == N (do không đủ độ chính xác) ... và

Câu hỏi của tôi là:

Nếu float thậm chí không thể đại diện cho 1e8, thì chúng tôi sẽ có tràn khi chúng tôi khởi tạo float x = 1e8; thì làm thế nào chúng ta sẽ làm cho máy tính chạy mãi mãi?

Tôi đã thử một ví dụ đơn giản ở đây (mặc dù không double nhưng int)

#include <iostream> 

int main() 
{ 
    int a = 4444444444444444444; 
    std::cout << "a " << a << std::endl; 
    return 0; 
} 
It outputs: a -1357789412 

Điều này có nghĩa rằng nếu trình biên dịch không có khả năng đại diện cho số lượng nhất định với int loại, nó sẽ gây ra tràn.

Vì vậy, tôi đã đọc sai? Điểm nào tôi nhớ? Đang thay đổi x từ double đến float hành vi chưa xác định?

Cảm ơn bạn!

Trả lời

9

Từ khóa là "chính xác".

float có thể đại diện cho 1e8, thậm chí chính xác, trừ khi bạn có loại quái dị float. Nhưng điều đó không có nghĩa là nó có thể đại diện cho tất cả các giá trị nhỏ hơn chính xác, ví dụ, thường là 2^25+1 = 33554433, cần 26 bit chính xác, không thể được biểu diễn chính xác trong float (thường, có độ chính xác 23 + 1 bit) và cũng không thể 2^25-1 = 33554431, cần 25 bit chính xác.

Cả hai con số này sau đó được biểu diễn dưới dạng 2^25 = 33554432, và sau đó

33554432.0f - 1 == 33554432.0f 

sẽ lặp. (Bạn sẽ nhấn một vòng trước đó, nhưng cái đó có biểu diễn số thập phân đẹp;)

Trong số học số nguyên, bạn có x - 1 != x cho tất cả x, nhưng không có trong số học dấu phẩy động. Lưu ý rằng vòng lặp cũng có thể kết thúc ngay cả khi float chỉ có độ chính xác 23 + 1 bit thông thường, vì tiêu chuẩn cho phép tính toán dấu phẩy động được thực hiện với độ chính xác cao hơn loại có, và nếu tính toán là được thực hiện ở độ chính xác cao hơn (ví dụ: double thông thường với 52 + 1 bit), mỗi phép trừ sẽ thay đổi x.

+0

cảm ơn vì thông tin tốt đẹp, nhưng câu hỏi của tôi là lý do tại sao vòng lặp while thậm chí sẽ bắt đầu nếu chúng ta có tràn lúc khởi tạo? – taocp

+2

@taocp Bạn không bị tràn khi khởi tạo. Các định dạng điểm nổi có liên tục thấp hơn * độ chính xác * khi bạn đi đến các giá trị cao hơn cho đến khi bạn đạt đến + 1. # INF. Hoạt động trên vô hạn và # NAN là hợp pháp, không được xác định. Họ có thể có kết quả bất ngờ mặc dù. Hãy xem [tiêu chuẩn IEEE-754] (https://en.wikipedia.org/wiki/IEEE_floating_point) –

+0

@indeterminatelysequenced ah, cảm ơn rất nhiều vì điều đó. – taocp

0

Hãy thử sửa đổi đơn giản này mà giá trị của các giá trị x liên tiếp.

#include <iostream> 
using namespace std; 

int main() 
{ 
    float x = 1e8; 
    while(x > 0) 
    { 
     cout << x << endl; 
     --x; 
    } 
} 

Trên một số triển khai nổi, bạn sẽ thấy rằng giá trị của phao bị kẹt ở 1e8 hoặc trong khu vực đó. Điều này là do cách phao lưu trữ số. Float không thể (và cũng không thể biểu diễn chút giới hạn) đại diện cho tất cả các giá trị thập phân có thể, vì vậy khi bạn xử lý các giá trị rất lớn trong float, bạn có một số thập phân, được sử dụng cho một số sức mạnh. Vâng, nếu giá trị thập phân này kết thúc bằng một giá trị trong đó bit cuối cùng rơi ra, nghĩa là nó được làm tròn lên. Những gì bạn kết thúc với là một giá trị giảm xuống (sau đó sao lưu) cho chính nó.

Các vấn đề liên quan