Bất bình đẳng gây ra bởi sự thiếu chính xác


15

Ít nhất là trong Java, nếu tôi viết mã này:

float a = 1000.0F;
float b = 0.00004F;
float c = a + b + b;
float d = b + b + a;
boolean e = c == d;

giá trị của sẽ là . Tôi tin rằng điều này được gây ra bởi thực tế là phao rất hạn chế trong cách thể hiện chính xác các con số. Nhưng tôi không hiểu tại sao chỉ cần thay đổi vị trí của có thể gây ra sự bất bình đẳng này.efalsea

Tôi đã giảm s xuống một trong cả hai dòng 3 và 4 như dưới đây, tuy nhiên giá trị của trở thành :betrue

float a = 1000.0F;
float b = 0.00004F;
float c = a + b;
float d = b + a;
boolean e = c == d;

Chính xác thì chuyện gì đã xảy ra ở dòng 3 và 4? Tại sao hoạt động bổ sung với phao không liên kết?

Cảm ơn trước.


16
Như ví dụ của bạn cho thấy, bổ sung dấu phẩy động giao hoán. Nhưng nó không liên quan.
Yuval Filmus

1
Tôi khuyến khích bạn tìm các định nghĩa cơ bản lên. Cũng lưu ý rằng trình biên dịch phân tích cú pháp là (bổ sung liên kết ở bên trái). ( r + s ) + tr+s+t(r+s)+t
Yuval Filmus

2
Đối với một cách dễ dàng để xem tại sao điều này nên như vậy, hãy xem xét Xmột số rất lớn và Ymột số rất nhỏ, như vậy X + Y = X. Ở đây, X + Y + -Xsẽ bằng không. Nhưng X + -X + Ysẽ được Y.
David Schwartz


Câu trả lời:


20

Trong các triển khai điểm nổi điển hình, kết quả của một thao tác được tạo ra như thể hoạt động được thực hiện với độ chính xác vô hạn, và sau đó làm tròn đến số dấu phẩy động gần nhất.

So sánh b + a : Kết quả của mỗi thao tác được thực hiện với độ chính xác vô hạn là như nhau, do đó các kết quả chính xác vô hạn giống hệt nhau này được làm tròn theo một cách giống hệt nhau. Nói cách khác, phép cộng dấu phẩy động là giao hoán.một+bb+một

Lấy : b là số có dấu phẩy động. Với số dấu phẩy động nhị phân , 2 b cũng là số dấu phẩy động (số mũ lớn hơn một), vì vậy b + b được thêm vào mà không có bất kỳ lỗi làm tròn nào. Sau đó a được thêm vào giá trị chính xác b + b . Kết quả là giá trị chính xác 2 b + a , được làm tròn đến số dấu phẩy động gần nhất.b+b+mộtb2bb+bmộtb+b2b+một

Lấy : a + b được thêm vào và sẽ có lỗi làm tròn r , vì vậy chúng ta nhận được kết quả a + b + r . Thêm b và kết quả là giá trị chính xác 2 b + a + r , được làm tròn đến số dấu phẩy động gần nhất.một+b+bmột+brmột+b+rb2b+một+r

Vì vậy, trong một trường hợp, , làm tròn. Trong trường hợp khác, 2 b + a + r , làm tròn.2b+một2b+một+r

Tái bút Việc hai số cụ thể b cả hai phép tính có cho cùng một kết quả hay không phụ thuộc vào các số và vào sai số làm tròn trong phép tính a + b và thường khó dự đoán. Về nguyên tắc, sử dụng độ chính xác đơn hoặc kép không có sự khác biệt đối với vấn đề, nhưng vì sai số làm tròn là khác nhau, sẽ có các giá trị của a và b trong đó ở độ chính xác đơn, kết quả là bằng nhau và ngược lại, chúng không chính xác hoặc ngược lại. Độ chính xác sẽ cao hơn rất nhiều, nhưng vấn đề là hai biểu thức giống nhau về mặt toán học nhưng không giống nhau trong số học dấu phẩy động vẫn giữ nguyên.mộtbmột+b

PPS. Trong một số ngôn ngữ, số học dấu phẩy động có thể được thực hiện với độ chính xác cao hơn hoặc phạm vi số cao hơn so với các báo cáo thực tế. Trong trường hợp đó, nhiều khả năng (nhưng vẫn không được bảo đảm) rằng cả hai khoản tiền đều cho kết quả như nhau.

PPPS. Một bình luận hỏi liệu chúng ta có nên hỏi xem số dấu phẩy động có bằng nhau hay không. Hoàn toàn nếu bạn biết những gì bạn đang làm. Ví dụ: nếu bạn sắp xếp một mảng hoặc thực hiện một tập hợp, bạn sẽ gặp rắc rối khủng khiếp nếu bạn muốn sử dụng một số khái niệm "xấp xỉ bằng nhau". Trong giao diện người dùng đồ họa, bạn có thể cần tính toán lại kích thước đối tượng nếu kích thước của đối tượng đã thay đổi - bạn so sánh oldSize == newSize để tránh tính toán lại, biết rằng trong thực tế, bạn gần như không bao giờ có kích thước gần như giống hệt nhau và chương trình của bạn là chính xác ngay cả khi có một tính toán lại không cần thiết.


Trong trường hợp cụ thể này, b trở thành định kỳ khi được chuyển đổi thành nhị phân, do đó có lỗi làm tròn ở khắp mọi nơi.
André Souza Lemos

1
@ AndréSouzaLemos btrong câu trả lời này không phải là 0,00004, đó là những gì bạn nhận được sau khi chuyển đổi và làm tròn.
Alexey Romanov

"Trong các triển khai điểm nổi điển hình, kết quả của một thao tác được tạo ra như thể hoạt động được thực hiện với độ chính xác vô hạn, và sau đó được làm tròn đến số dấu phẩy động gần nhất." - điều đó thực sự bắt buộc bởi đặc tả khi tôi cố gắng thực hiện điều này theo các cổng logic (trình giả lập chỉ có thể xử lý các bus 64 bit).
John Dvorak

Câu hỏi ngây thơ: Liệu thử nghiệm cho bình đẳng float bao giờ có ý nghĩa? Tại sao hầu hết các ngôn ngữ lập trình đều cho phép aa == b kiểm tra trong đó cả hai hoặc một là dấu phẩy?
tò mò_cat

Định nghĩa có liên quan từ Wikipedia: " Máy Epsilon đưa ra giới hạn trên về lỗi tương đối do làm tròn số học dấu phẩy động."
Blackhawk

5

Định dạng dấu phẩy động nhị phân được hỗ trợ bởi máy tính về cơ bản tương tự như ký hiệu khoa học thập phân được sử dụng bởi con người.

Một số dấu phẩy động bao gồm một dấu, mantissa (chiều rộng cố định) và số mũ (chiều rộng cố định), như sau:

+/-  1.0101010101 × 2^12345
sign   ^mantissa^     ^exp^

Ký hiệu khoa học thông thường có định dạng tương tự:

+/- 1.23456 × 10^99

Nếu chúng ta thực hiện số học trong ký hiệu khoa học với độ chính xác hữu hạn, làm tròn sau mỗi thao tác, thì chúng ta sẽ nhận được tất cả các tác động xấu tương tự như dấu phẩy động nhị phân.


Thí dụ

Để minh họa, giả sử chúng ta sử dụng chính xác 3 chữ số sau dấu thập phân.

a = 99990 = 9.999 × 10^4
b =     3 = 3.000 × 10^0

(a + b) + b

Bây giờ chúng tôi tính toán:

c = a + b
  = 99990 + 3      (exact)
  = 99993          (exact)
  = 9.9993 × 10^4  (exact)
  = 9.999 × 10^4.  (rounded to nearest)

Trong bước tiếp theo, tất nhiên:

d = c + b
  = 99990 + 3 = ...
  = 9.999 × 10^4.  (rounded to nearest)

Do đó (a + b) + b = 9,999 × 10 4 .

(b + b) + a

Nhưng nếu chúng tôi thực hiện các hoạt động theo một thứ tự khác:

e = b + b
  = 3 + 3  (exact)
  = 6      (exact)
  = 6.000 × 10^0.  (rounded to nearest)

Tiếp theo chúng tôi tính toán:

f = e + a
  = 6 + 99990      (exact)
  = 99996          (exact)
  = 9.9996 × 10^4  (exact)
  = 1.000 × 10^5.  (rounded to nearest)

Do đó (b + b) + a = 1.000 × 10 5 , khác với câu trả lời khác của chúng tôi.


5

Java sử dụng biểu diễn dấu phẩy động nhị phân IEEE 754, dành 23 chữ số nhị phân cho lớp phủ, được chuẩn hóa để bắt đầu bằng chữ số có nghĩa đầu tiên (bỏ qua, để tiết kiệm không gian).

0,0000410= =0,0000000000000000101001111100010110101100010001110001101101000111 ...2= =[1.]01001111100010110101100010001110001101101000111 ...2×2-15

100010+0,0000410= =1111101000.00000000000000101001111100010110101100010001110001101101000111 ...2= =[1.]11110100000000000000000101001111100010110101100010001110001101101000111 ...2×29

Các phần màu đỏ là bọ ngựa, vì chúng thực sự được đại diện (trước khi làm tròn).

(100010+0,0000410)+0,0000410(0,0000410+0,0000410)+100010


0

Gần đây chúng tôi đã gặp phải một vấn đề làm tròn tương tự. Các câu trả lời được đề cập ở trên là chính xác, tuy nhiên khá kỹ thuật.

Tôi tìm thấy những điều sau đây là một lời giải thích tốt về lý do tại sao các lỗi làm tròn tồn tại. http://csharpindepth.com/Articles/General/FloatingPoint.aspx

TLDR: các điểm nổi nhị phân không thể được ánh xạ chính xác đến các dấu phẩy động thập phân. Điều này gây ra sự không chính xác có thể kết hợp trong các hoạt động toán học.

Một ví dụ sử dụng số trôi nổi thập phân: 1/3 + 1/3 + 1/3 thường sẽ bằng 1. Tuy nhiên, trong số thập phân: 0.333333 + 0.333333 + 0.333333 không bao giờ chính xác bằng 1.000000

Điều tương tự cũng xảy ra khi thực hiện các phép toán trên số thập phân nhị phân.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.