2010-03-23 32 views
8

Tôi có bit sau đây, tuy nhiên khi biên dịch với GCC 4.4 với các cờ tối ưu hóa khác nhau, tôi nhận được một số kết quả không mong muốn khi chạy.Vấn đề GCC với so sánh hai loại thô

#include <iostream> 

int main() 
{ 
    const unsigned int cnt = 10; 
    double lst[cnt] = { 0.0 }; 
    const double v[4] = { 131.313, 737.373, 979.797, 731.137 }; 

    for(unsigned int i = 0; i < cnt; ++i) { 
     lst[i] = v[i % 4] * i; 
    } 

    for(unsigned int i = 0; i < cnt; ++i) { 
     double d = v[i % 4] * i; 
     if(lst[i] != d) { 
     std::cout << "error @ : " << i << std::endl; 
     return 1; 
     } 
    } 
    return 0; 
} 
  • khi biên soạn với: "g ++ -pedantic Wall -Werror -O1 -o test test.cpp" tôi nhận được kết quả như sau: "Lỗi @: 3"

  • khi biên soạn với: "g ++ -pedantic Wall -Werror -O2 -o test test.cpp" tôi nhận được kết quả như sau: "lỗi @: 3"

  • khi biên soạn với: "g ++ -pedantic Wall -Werror O3 -o test test.cpp" tôi nhận được không có lỗi

  • khi biên soạn với: "g ++ -pedantic Wall - Werror -o test test.cpp" tôi nhận được không có lỗi

tôi không tin rằng đây là một vấn đề liên quan đến làm tròn, hoặc chênh lệch epsilon trong so sánh. Tôi đã thử điều này với Intel v10 và MSVC 9,0 và tất cả họ dường như làm việc như mong đợi. Tôi tin rằng điều này sẽ không có gì hơn một so sánh bitwise.

Nếu tôi thay thế if-tuyên bố như sau: if (static_cast<long long int>(lst[i]) != static_cast<long long int>(d)), và thêm "-Wno dài dài" tôi nhận được không có lỗi trong bất kỳ chế độ tối ưu hóa khi chạy.

Nếu tôi thêm std::cout << d << std::endl; trước khi "return 1", tôi nhận được không có lỗi trong bất kỳ chế độ tối ưu hóa khi chạy.

Đây có phải là lỗi trong mã của tôi hay không, có điều gì sai với GCC và cách xử lý loại kép không?

Lưu ý: Tôi vừa thử với phiên bản gcc 4.3 và 3.3, lỗi không được hiển thị.

Độ phân giải: Mike Dinsdale ghi nhận trong báo cáo lỗi sau: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=323 Có vẻ như đội GCC không hoàn toàn chắc chắn về bản chất của vấn đề.

Như được đề xuất trong báo cáo lỗi, độ phân giải có thể là sử dụng tùy chọn lưu trữ ffloat. Tôi đã thử điều này và nó hoạt động, tuy nhiên kết quả từ một quan điểm thực hiện không phải là tuyệt vời, mặc dù ymmv.

+1

So sánh 'gấp đôi' được thực hiện tốt nhất bằng cách sử dụng một giá trị delta nhỏ để tính toán các điểm không chính xác. Thử in các giá trị của 'lst [i]' và 'd' ngay trước khi so sánh và cho chúng ta biết chúng khác nhau như thế nào. – dirkgently

+3

Đây không phải là một kịch bản mà tôi có giá trị được tạo từ các nguồn khác nhau và tôi muốn xem liệu chúng có bằng nhau hay không, đây là những giá trị giống nhau (bitwise), chúng nên so sánh đúng. Thực tế là trình biên dịch không tạo ra một tệp nhị phân cung cấp một tập hợp đầu ra nhất quán hoặc là cách đáng lo ngại. –

+0

@Monomer: Hãy xem xét rằng khi bạn thay đổi chương trình để sử dụng một so sánh bitwise isntead của FPU, bạn đang thay đổi lắp ráp. Có vẻ cơ bản, nhưng điều này có nghĩa là nó sẽ không sử dụng các giá trị trên FPU, có thể bị tắt, và thực sự tải và có thể cắt bớt chúng. Nói một cách đơn giản, bài kiểm tra của bạn không kết luận. – GManNickG

Trả lời

4

Vấn đề là khả năng kết quả của mất một số chính xác khi lưu trữ kết quả của một biểu thức so với các trình biên dịch không làm như vậy trong một địa phương như là một tối ưu hóa:

double d = v[i % 4] * i; // the result, `d`, might be kept in a register 
          // instead of storing it in a memory location, 
          // keeping full precision 

if(lst[i] != d) {   // the value stored in lst[i] may have lost some 
          // precision since it had to be stored in memory, 
          // which might not be able to hold the full 
          // precision that the expression generated 

Tiêu chuẩn C99 nói trong 6.3.1 .8/2 "Chuyển đổi số học thông thường":

Giá trị của toán hạng nổi và kết quả của biểu thức nổi có thể là đại diện cho độ chính xác và phạm vi lớn hơn yêu cầu của loại; các loại không phải là đã thay đổi.

+1

Lỗi này không được nhìn thấy khi không tối ưu hóa được kích hoạt theo tùy chọn thứ 4, cũng trên cùng một hệ thống (ubuntu) và trên các cửa sổ bằng cách sử dụng intel v10, lỗi không được nhìn thấy. –

+3

@Monomer: Tác động của hành vi này rất có thể bị ảnh hưởng mạnh bởi cài đặt triển khai, nền tảng và tối ưu hóa. Loại điều này là một trong những lý do tại sao tôi khá hạnh phúc mà tôi hiếm khi phải đối phó với điểm nổi. –

+1

Tôi đồng ý, các vấn đề về dấu phẩy động đã là nguyên nhân của sự tồn tại của tôi trong vài năm qua, bạn nghĩ rằng bạn đã nhìn thấy tất cả rồi đột nhiên một cái gì đó khác xuất hiện. –

3

Chiều rộng của thanh ghi dấu phẩy động trong x86 khác với chiều rộng của double trong RAM. Do đó việc so sánh có thể thành công hay thất bại tùy thuộc hoàn toàn vào cách trình biên dịch quyết định tối ưu hóa tải trọng của các giá trị dấu phẩy động.

+0

đó là chính xác, tuy nhiên không nên trình biên dịch tạo ra mã chính xác cho các biểu thức nhất định? –

+0

Nó tạo mã đúng. Làm thế nào là nó không chính xác? Trong một ví dụ thập phân, 3.333 không bằng 3.333333. Đó là cách FPU nhìn thấy nó, chỉ ở dạng nhị phân thay vì số thập phân. –

+2

điểm tôi đang cố gắng thực hiện là các giá trị trên cả hai mặt của "==" được tạo theo cách tương tự. Tôi hiểu rằng để tăng gấp đôi a == b và b == a có thể không tạo ra kết quả tương tự. Nhưng, những giá trị này giống hệt nhau, nếu chúng không phải là cách tiếp cận so sánh dài lâu sẽ không hoạt động. Ngoài ra làm thế nào để giải thích toàn bộ điều làm việc, nếu tôi có một tuyên bố in thêm? không có mùi một chút? –

6

Thực tế là kết quả phụ thuộc vào các thiết lập tối ưu hóa cho thấy nó có thể là x87 mở rộng chính xác rối tung với những thứ (như Michael Burr nói).

Dưới đây là một số mã tôi sử dụng (với gcc trên bộ xử lý x86) để chuyển đổi chính xác mở rộng off:

static const unsigned int PRECISION_BIT_MASK = 0x300; 
///< bitmask to mask out all non-precision bits in the fpu control word \cite{INTEL}. 
static const unsigned int EXTENDED_PRECISION_BITS = 0x300; 
///< add to the fpu control word (after zeroing precision bits) to turn on extended precision \cite{INTEL}. 
static const unsigned int STANDARD_PRECISION_BITS = 0x200; 
///< add to the fpu control word (after zeroing precision bits) to turn off extended precision \cite{INTEL}. 

void set_fpu_control_word(unsigned int mode) 
{ 
    asm ("fldcw %0" : : "m" (*&mode)); 
} 

unsigned int get_fpu_control_word() 
{ 
    volatile unsigned int mode = 0; 
    asm ("fstcw %0" : "=m" (*&mode)); 
    return mode; 
} 

bool fpu_set_extended_precision_is_on(bool state) 
{ 
    unsigned int old_cw = get_fpu_control_word(); 
    unsigned int masked = old_cw & ~PRECISION_BIT_MASK; 
    unsigned int new_cw; 
    if(state) 
    new_cw = masked + EXTENDED_PRECISION_BITS; 
    else 
    new_cw = masked + STANDARD_PRECISION_BITS; 
    set_fpu_control_word(new_cw); 
    return true; 
} 

bool fpu_get_extended_precision_is_on() 
{ 
    unsigned int old_cw = get_fpu_control_word(); 
    return ((old_cw & PRECISION_BIT_MASK) == 0x300); 
} 

Hoặc bạn chỉ có thể chạy mã của bạn với valgrind, mà không mô phỏng các thanh ghi 80-bit và có lẽ dễ dàng hơn cho một chương trình ngắn như thế này!

+0

@Mike: Từ điều khiển được đặt trên, tôi nghĩ rằng đây là vấn đề về các điểm nổi 64 bit bị hạ cấp xuống các điểm nổi 32 bit. –

+3

Để 32 bit? Bạn đã kiểm tra sự khác biệt và nó thực sự lớn đến mức nào? Nếu vậy, có vẻ như một lỗi trong gcc. Nếu tôi không cược rằng nó là 80-bit -> 64-bit cắt ngắn gây ra bằng cách tiết kiệm đăng ký FPU vào bộ nhớ. Bạn đã cài đặt valgrind chưa? Nếu vậy tôi sẽ kiểm tra xem vấn đề biến mất chạy mã của bạn thông qua valgrind. –

+0

@ Giống như: một lỗi liên quan đến việc lưu trạng thái CPU, vì nếu tôi truy cập giá trị "d" một lần nữa (còn gọi là lệnh in cho d) thì vấn đề sẽ biến mất. –