2012-04-29 49 views
7

Tôi đã có một vấn đề khi tôi đã bổ sung thêm ba giá trị dấu chấm động và so sánh chúng với 1.Là bổ sung dấu chấm động và liên kết nhân?

cout << ((0.7 + 0.2 + 0.1)==1)<<endl;  //output is 0 
cout << ((0.7 + 0.1 + 0.2)==1)<<endl;  //output is 1 

Tại sao những giá trị này sẽ đi ra khác nhau?

+0

Mã mẫu của bạn khác với * commutativity *, không * kết hợp *. Một phiên bản thể hiện tính kết hợp sẽ là '(0.7 + (0.1 + 0.2))' –

+0

@MattMcNabb: + là một phép toán nhị phân. Với toán hạng dấu phẩy động, nó là giao hoán nhưng không liên kết. Do đó, nếu bạn có hai biểu thức tạo ra các kết quả khác nhau, bạn không thể tạo thành một biểu thức từ cái kia bằng cách chỉ áp dụng tính tương giao. – tmyklebu

+0

@tmyklebu OK, do đó, điều này không kiểm tra tính kết hợp nếu và chỉ khi nó được biết rằng commutativity nắm giữ. (Tiêu chuẩn C++ không xuất hiện để đảm bảo tính tương giao). –

Trả lời

10

Ngoài ra điểm nổi không nhất thiết phải liên kết. Nếu bạn thay đổi thứ tự mà bạn thêm mọi thứ, điều này có thể thay đổi kết quả.

Giấy tiêu chuẩn về chủ thể là What Every Computer Scientist Should Know about Floating Point Arithmetic. Nó đưa ra ví dụ sau:

Khu vực màu xám khác liên quan đến việc diễn giải các dấu ngoặc đơn. Do lỗi tròn, luật liên kết đại số không nhất thiết phải giữ cho các số dấu phẩy động. Ví dụ, biểu thức (x + y) + z có một câu trả lời hoàn toàn khác với x + (y + z) khi x = 1e30, y = -1e30 và z = 1 (nó là 1 trong trường hợp trước, 0 trong trường hợp sau).

5

là gì khả năng, với các máy hiện đang phổ biến và phần mềm, là:

Trình biên dịch mã hóa .7 như 0x1.6666666666666p-1 (đây là hệ thập lục phân số 1,6666666666666 nhân 2 với sức mạnh của -1), .2 dưới dạng 0x1.999999999999ap-3 và .1 dưới dạng 0x1.999999999999ap-4. Mỗi trong số này là số đại diện trong dấu phẩy động gần nhất với số thập phân bạn đã viết.

Quan sát rằng mỗi hằng số dấu phẩy động thập lục phân này có chính xác 53 bit theo nghĩa của nó và (phần "phân số", thường không chính xác được gọi là phần định trị). Số thập lục phân cho significand có chữ số "1" và mười ba chữ số thập lục phân (bốn bit mỗi bit, 52 tổng, 53 bao gồm "1"), đó là những gì chuẩn IEEE-754 cung cấp, cho nhị phân 64 bit nổi- số điểm.

Hãy thêm các số cho .7 và .2: 0x1.6666666666666p-1 và 0x1.999999999999ap-3. Đầu tiên, tỷ lệ số mũ của số thứ hai để khớp với số thứ nhất. Để thực hiện điều này, chúng ta sẽ nhân số mũ bằng 4 (thay đổi "p-3" thành "p-1") và nhân số mũ và 1/4, cho 0x0.66666666666668p-1. Sau đó, thêm 0x1.6666666666666p-1 và 0x0.66666666666668p-1, tặng 0x1.ccccccccccccc8p-1. Lưu ý rằng con số này có hơn 53 bit trong điều kiện: "8" là chữ số thứ 14 sau dấu chấm. Dấu phẩy động không thể trả về kết quả với nhiều bit này, vì vậy nó phải được làm tròn đến số đại diện gần nhất. Trong trường hợp này, có hai số gần bằng nhau, 0x1.cccccccccccccp-1 và 0x1.ccccccccccccdp-1. Khi có một tie, số với một số không trong bit thấp nhất của significand được sử dụng. "c" là chẵn và "d" là số lẻ, vì vậy "c" được sử dụng. Kết quả cuối cùng của phần bổ sung là 0x1.cccccccccccccp-1.

Tiếp theo, thêm số cho .1 (0x1.999999999999ap-4) vào đó. Một lần nữa, chúng tôi mở rộng để làm cho các số mũ khớp nhau, vì vậy 0x1.999999999999ap-4 trở thành 0x.33333333333334p-1. Sau đó thêm nó vào 0x1.cccccccccccccp-1, cho 0x1.fffffffffffff4p-1. Làm tròn đến 53 bit cho 0x1.fffffffffffffp-1, và đó là kết quả cuối cùng của ".7 + .2 + .1".

Bây giờ hãy xem xét ".7 + .1 + .2". Đối với ".7 + .1", hãy thêm 0x1.6666666666666p-1 và 0x1.999999999999ap-4. Nhớ lại cái sau được chia tỷ lệ thành 0x.33333333333334p-1. Sau đó, tổng chính xác là 0x1.99999999999994p-1. Làm tròn đến 53 bit cho 0x1.9999999999999p-1.

Sau đó, thêm số cho .2 (0x1.999999999999ap-3), được chia tỷ lệ thành 0x0.66666666666668p-1. Tổng chính xác là 0x2.00000000000008p-1. Các điểm nghĩa của dấu chấm động luôn được chia tỷ lệ để bắt đầu bằng 1 (ngoại trừ các trường hợp đặc biệt: số không, vô cực và các số rất nhỏ ở cuối phạm vi biểu diễn), vì vậy chúng tôi điều chỉnh điều này thành 0x1.00000000000004p0.Cuối cùng, chúng tôi làm tròn tới 53 bit, cho 0x1.0000000000000p0.

Do đó, do lỗi xảy ra khi làm tròn, ".7 + .2 + .1" trả về 0x1.fffffffffffffp-1 (rất nhỏ hơn 1) và ".7 + .1 + .2" trả về 0x1.0000000000000p0 (chính xác 1).

1

Nhân dấu chấm động không phải là kết hợp trong C hoặc C++.

Proof:

#include<stdio.h> 
#include<time.h> 
#include<stdlib.h> 
using namespace std; 
int main() { 
    int counter = 0; 
    srand(time(NULL)); 
    while(counter++ < 10){ 
     float a = rand()/100000; 
     float b = rand()/100000; 
     float c = rand()/100000; 

     if (a*(b*c) != (a*b)*c){ 
      printf("Not equal\n"); 
     } 
    } 
    printf("DONE"); 
    return 0; 
} 

Trong chương trình này, khoảng 30% thời gian, (a*b)*c không bằng a*(b*c).

+3

hoặc 0% thời gian nếu 'RAND_MAX <100000'! –

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