2012-11-12 30 views
16

Tôi đã gặp phải một chút gì đó khó hiểu khi cố giải quyết vấn đề số học dấu chấm động.Tại sao GDB đánh giá số học dấu phẩy động khác với C++?

Đầu tiên, mã. Tôi đã cất bản chất của vấn đề của tôi vào ví dụ này:

#include <iostream> 
#include <iomanip> 

using namespace std; 
typedef union {long long ll; double d;} bindouble; 

int main(int argc, char** argv) { 
    bindouble y, z, tau, xinum, xiden; 
    y.d = 1.0d; 
    z.ll = 0x3fc5f8e2f0686eee; // double 0.17165791262311053 
    tau.ll = 0x3fab51c5e0bf9ef7; // double 0.053358253178712838 
    // xinum = double 0.16249854626123722 (0x3fc4ccc09aeb769a) 
    xinum.d = y.d * (z.d - tau.d) - tau.d * (z.d - 1); 
    // xiden = double 0.16249854626123725 (0x3fc4ccc09aeb769b) 
    xiden.d = z.d * (1 - tau.d); 
    cout << hex << xinum.ll << endl << xiden.ll << endl; 
} 

xinumxiden nên có giá trị như nhau (khi y == 1), nhưng vì dấu chấm động lỗi roundoff họ thì không. Đó là phần tôi nhận được.

Câu hỏi xuất hiện khi tôi chạy mã này (thực sự, chương trình thực sự của tôi) thông qua GDB để theo dõi sự khác biệt. Nếu tôi sử dụng GDB để tạo lại đánh giá thực hiện trong các mã, nó mang lại cho một kết quả khác nhau cho xiden:

$ gdb mathtest 
GNU gdb (Gentoo 7.5 p1) 7.5 
... 
This GDB was configured as "x86_64-pc-linux-gnu". 
... 
(gdb) break 16 
Breakpoint 1 at 0x4008ef: file mathtest.cpp, line 16. 
(gdb) run 
Starting program: /home/diazona/tmp/mathtest 
... 
Breakpoint 1, main (argc=1, argv=0x7fffffffd5f8) at mathtest.cpp:16 
16   cout << hex << xinum.ll << endl << xiden.ll << endl; 
(gdb) print xiden.d 
$1 = 0.16249854626123725 
(gdb) print z.d * (1 - tau.d) 
$2 = 0.16249854626123722 

Bạn sẽ nhận thấy rằng nếu tôi hỏi GDB để tính toán z.d * (1 - tau.d), nó mang lại cho 0,16249854626123722 (0x3fc4ccc09aeb769a), trong khi mã C++ thực tế tính toán điều tương tự trong chương trình cho 0.16249854626123725 (0x3fc4ccc09aeb769b). Vì vậy, GDB phải sử dụng một mô hình đánh giá khác cho số học dấu phẩy động. Có ai có thể làm sáng tỏ thêm về điều này không? Đánh giá của GDB khác với đánh giá của bộ xử lý của tôi như thế nào?

Tôi đã xem this related question hỏi về GDB đánh giá sqrt(3) đến 0, nhưng điều này không phải là điều tương tự vì không có cuộc gọi chức năng nào có liên quan ở đây.

Trả lời

2

Không phải GDB của nó so với bộ xử lý, đó là bộ nhớ so với bộ xử lý. Bộ xử lý x64 lưu trữ nhiều bit chính xác hơn so với bộ nhớ thực sự nắm giữ (80 bit so với 64 bit). Miễn là nó vẫn nằm trong CPU và thanh ghi, nó vẫn giữ được độ chính xác 80 bit, nhưng khi nó được gửi đến bộ nhớ sẽ xác định khi nào và do đó nó được làm tròn như thế nào. Nếu GDB gửi tất cả các kết quả tính toán gián đoạn ra khỏi CPU (Tôi không biết nếu đây là trường hợp hoặc ở bất kỳ vị trí nào), nó sẽ thực hiện làm tròn tại mỗi bước, dẫn đến kết quả hơi khác.

5

Có thể vì FPU x86 hoạt động trong sổ đăng ký với độ chính xác 80 bit, nhưng làm tròn đến 64 bit khi giá trị được lưu vào bộ nhớ. GDB sẽ được lưu trữ vào bộ nhớ trên mỗi bước của (tính toán) tính toán.

+0

Thực tế, kết quả của gdb là kết quả chính xác hơn, do đó, có vẻ như gdb sử dụng độ chính xác lớn hơn của FPU, trong khi g ++ có thể sử dụng lệnh SSE. –

4

Hệ thống đánh giá biểu thức thời gian chạy của GDB chắc chắn không được đảm bảo để thực thi cùng một mã máy hiệu quả cho các thao tác dấu chấm động của bạn như mã máy được tối ưu hóa và sắp xếp lại được tạo ra bởi trình biên dịch của bạn. Thật vậy, nó được đảm bảo không thực thi cùng một mã máy để tính toán giá trị của biểu thức đã cho z.d * (1 - tau.d), vì đây có thể được coi là tập hợp con của chương trình của bạn mà việc đánh giá biểu thức bị cô lập được thực hiện trong thời gian chạy một cách tùy ý. đường.

Tạo mã dấu chấm động và thực hiện đầu ra của nó bằng CPU đặc biệt dễ bị biểu hiện không thống nhất với các triển khai khác (chẳng hạn như bộ đánh giá biểu thức thời gian chạy) do tối ưu hóa (thay thế, sắp xếp lại, loại trừ phụ, v.v.) hướng dẫn, lựa chọn phân bổ đăng ký và môi trường dấu phẩy động. Nếu đoạn mã của bạn chứa nhiều biến tự động trong biểu thức tạm thời (như của bạn), tạo mã có số lượng tự do đặc biệt lớn với số lần tối ưu hóa bằng 0, và với tự do đó có khả năng-- trong trường hợp này-- mất độ chính xác trong bit ít quan trọng nhất theo cách có vẻ không phù hợp.

Bạn sẽ không hiểu rõ lý do tại sao trình đánh giá thời gian chạy của GDB thực thi bất kỳ hướng dẫn nào mà nó đã hiểu sâu sắc về mã nguồn GDB, cài đặt xây dựng và mã tạo thời gian biên dịch của riêng nó.

Bạn có thể đạt mức cao nhất tại hội đồng được tạo cho quy trình của bạn để biết cách các cửa hàng cuối cùng thành z, tau và [ngược lại] xiden hoạt động. Luồng dữ liệu cho các hoạt động dấu phẩy động dẫn đến các cửa hàng đó có lẽ không giống như vậy. Dễ dàng hơn nhiều, hãy thử làm cho việc tạo mã xác định hơn bằng cách vô hiệu hóa tất cả tối ưu hóa trình biên dịch (ví dụ: -O0 trên GCC) và viết lại các biểu thức dấu phẩy động để sử dụng không có biến thời gian/tự động. Sau đó, phá vỡ mọi dòng trong GDB và so sánh.

Tôi ước rằng tôi có thể cho bạn biết chính xác lý do tại sao mà bit kém quan trọng nhất của mantissa bị lật, nhưng sự thật là, bộ xử lý thậm chí không "biết" tại sao một cái gì đó mang một chút và cái gì khác không phải do , ví dụ, thứ tự đánh giá mà không có một hướng dẫn đầy đủ và theo dõi dữ liệu của cả mã của bạn và chính GDB.

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