Nếu tôi sao chép một float sang một biến khác, liệu chúng có bằng nhau không?


167

Tôi biết rằng sử dụng ==để kiểm tra sự bằng nhau của các biến dấu phẩy động không phải là một cách tốt. Nhưng tôi chỉ muốn biết rằng với các tuyên bố sau:

float x = ...

float y = x;

assert(y == x)

yđược sao chép từ x, khẳng định sẽ là đúng?


78
Hãy để tôi cung cấp tiền thưởng 50 cho một người thực sự chứng minh sự bất bình đẳng bằng một cuộc biểu tình với mã thực. Tôi muốn thấy điều 80 vs 64 bit đang hoạt động. Cộng thêm 50 giải thích cho mã trình biên dịch mã được tạo ra cho thấy một biến trong một thanh ghi và biến kia không (hoặc bất kỳ lý do nào cho sự bất bình đẳng có thể là, tôi muốn nó giải thích ở mức độ thấp).
Thomas Weller

1
@ThomasWeller lỗi GCC về điều này: gcc.gnu.org/ormszilla/show_orms.cgi?id=323 ; tuy nhiên, tôi vừa thử repro nó trên hệ thống x86-64 và nó không, ngay cả với toán học -ffast. Tôi nghi ngờ bạn cần một GCC cũ trên hệ thống 32 bit.
pjc50

5
@ pjc50: Thật ra bạn cần một hệ thống 80 bit để tái tạo lỗi 323; Đó là FPU 80x87 gây ra sự cố. x86-64 sử dụng SSE FPU. Các bit thừa gây ra vấn đề, bởi vì chúng được làm tròn khi đổ giá trị lên mức nổi 32 bit.
MSalters

4
Nếu lý thuyết của MSalters là đúng (và tôi nghi ngờ là như vậy), thì bạn có thể repro bằng cách biên dịch cho 32-bit ( -m32) hoặc bằng cách hướng dẫn GCC sử dụng x87 FPU ( -mfpmath=387).
Cody Grey

4
Thay đổi "48 bit" thành "80 bit", và sau đó bạn có thể xóa tính từ "huyền thoại" ở đó, @Hot. Đó chính xác là những gì đã được thảo luận ngay trước khi bình luận của bạn. X87 (FPU cho kiến ​​trúc x86) sử dụng các thanh ghi 80 bit, định dạng "độ chính xác mở rộng".
Cody Grey

Câu trả lời:


125

Bên cạnh assert(NaN==NaN);trường hợp được chỉ ra bởi kmdreko, bạn có thể gặp tình huống với x87-math, khi các phao 80 bit được lưu tạm thời vào bộ nhớ và sau đó so với các giá trị vẫn được lưu trữ trong một thanh ghi.

Ví dụ tối thiểu có thể xảy ra với gcc9.2 khi được biên dịch với -O2 -m32:

#include <cassert>

int main(int argc, char**){
    float x = 1.f/(argc+2);
    volatile float y = x;
    assert(x==y);
}

Bản giới thiệu của Godbolt: https://godbolt.org/z/X-Xt4R

volatilethể bỏ qua, nếu bạn quản lý để tạo đủ áp lực đăng ký để ylưu trữ và tải lại từ bộ nhớ (nhưng nhầm lẫn trình biên dịch đủ, không bỏ qua so sánh tất cả cùng nhau).

Xem tài liệu tham khảo Câu hỏi thường gặp của GCC:


2
Có vẻ lạ khi các bit thừa sẽ được xem xét khi so sánh floatvới độ chính xác tiêu chuẩn với độ chính xác cao.
Nat

13
@Nat Nó lạ; đây là một lỗi .
Cuộc đua nhẹ nhàng trong quỹ đạo

13
@ThomasWeller Không, đó là một giải thưởng hợp lý. Mặc dù tôi muốn câu trả lời chỉ ra rằng đây là hành vi không tuân thủ
Các cuộc đua nhẹ nhàng trong quỹ đạo

4
Tôi có thể mở rộng câu trả lời này, chỉ ra chính xác những gì xảy ra trong mã lắp ráp và điều này thực sự vi phạm tiêu chuẩn - mặc dù tôi sẽ không tự gọi mình là luật sư ngôn ngữ, vì vậy tôi không thể đảm bảo rằng không có gì khó hiểu mệnh đề cho phép rõ ràng hành vi đó. Tôi cho rằng OP quan tâm nhiều hơn đến các biến chứng thực tế trên các trình biên dịch thực tế, chứ không phải các trình biên dịch tuân thủ hoàn toàn không có lỗi (mà thực tế không tồn tại, tôi đoán vậy).
chtz

4
Đáng nói là -ffloat-storedường như là cách để ngăn chặn điều này.
OrangeDog

116

Nó sẽ không thể đúng nếu xNaN, kể từ khi so sánh trên NaNluôn luôn sai (có, thậm chí NaN == NaN). Đối với tất cả các trường hợp khác (giá trị bình thường, giá trị không bình thường, vô số, số không), khẳng định này sẽ đúng.

Lời khuyên cho việc tránh ==phao áp dụng cho các phép tính do số dấu phẩy động không thể biểu thị nhiều kết quả chính xác khi được sử dụng trong các biểu thức số học. Chuyển nhượng không phải là một phép tính và không có lý do gì mà chuyển nhượng sẽ mang lại một giá trị khác với giá trị ban đầu.


Đánh giá độ chính xác mở rộng phải là một vấn đề nếu tiêu chuẩn được tuân theo. Từ <cfloat>kế thừa từ C [5.2.4.2.2.8] ( nhấn mạnh của tôi ):

Ngoại trừ việc gán và truyền (loại bỏ tất cả phạm vi và độ chính xác bổ sung) , các giá trị của các phép toán với toán hạng nổi và giá trị tuân theo các chuyển đổi số học thông thường và các hằng số trôi nổi được đánh giá theo định dạng có phạm vi và độ chính xác có thể lớn hơn yêu cầu của kiểu.

Tuy nhiên, như các ý kiến ​​đã chỉ ra, một số trường hợp với một số trình biên dịch, tùy chọn xây dựng và mục tiêu nhất định có thể làm cho điều này nghịch lý.


10
Điều gì xảy ra nếu xđược tính toán trong một thanh ghi trong dòng đầu tiên, giữ độ chính xác cao hơn mức tối thiểu cho a float. Có y = xthể trong bộ nhớ, chỉ giữ floatđộ chính xác. Sau đó, kiểm tra cho sự bằng nhau sẽ được thực hiện với bộ nhớ so với thanh ghi, với các quy tắc khác nhau, và do đó không có gì đảm bảo.
David Schwartz

5
x+pow(b,2)==x+pow(a,3)có thể khác với auto one=x+pow(b,2); auto two=y+pow(a,3); one==twovì người ta có thể so sánh sử dụng độ chính xác cao hơn so với cái kia (nếu một / hai là giá trị 64 bit trong ram, trong khi giá trị trung gian là 80 bit trên fpu). Vì vậy, sự phân công có thể làm một cái gì đó, đôi khi.
Yakk - Adam Nevraumont

22
@evg Chắc chắn rồi! Câu trả lời của tôi chỉ đơn giản là theo tiêu chuẩn. Tất cả các cược được tắt nếu bạn nói với trình biên dịch của bạn là không liên kết, đặc biệt là khi kích hoạt tính toán nhanh.
kmdreko

11
@Voo Xem trích dẫn trong câu trả lời của tôi. Giá trị của RHS được gán cho biến trên LHS. Không có sự biện minh pháp lý nào cho giá trị kết quả của LHS khác với giá trị của RHS. Tôi đánh giá cao rằng một số trình biên dịch có lỗi về vấn đề này. Nhưng cho dù một cái gì đó được lưu trữ trong một sổ đăng ký được cho là không có gì để làm với nó.
Cuộc đua nhẹ nhàng trong quỹ đạo

6
@Voo: Trong ISO C ++, làm tròn đến độ rộng loại được cho là xảy ra trên bất kỳ nhiệm vụ nào. Trong hầu hết các trình biên dịch nhắm mục tiêu x87, nó thực sự chỉ xảy ra khi trình biên dịch quyết định làm đổ / tải lại. Bạn có thể buộc nó gcc -ffloat-storetuân thủ nghiêm ngặt. Nhưng câu hỏi này là về x=y; x==y; mà không làm bất cứ điều gì để var ở giữa. Nếu yđã được làm tròn để phù hợp với một số float, chuyển đổi thành gấp đôi hoặc dài gấp đôi và trở lại sẽ không thay đổi giá trị. ...
Peter Cordes

34

Có, ychắc chắn sẽ đảm nhận giá trị của x:

[expr.ass]/2: Trong phép gán đơn giản (=), đối tượng được gọi bằng toán hạng bên trái được sửa đổi ([defns.access]) bằng cách thay thế giá trị của nó bằng kết quả của toán hạng bên phải.

Không có thời gian cho các giá trị khác được chỉ định.

(Những người khác đã chỉ ra rằng một so sánh tương đương ==dù sao cũng sẽ đánh giá falsecác giá trị NaN.)

Vấn đề thường gặp với dấu phẩy động ==là thật dễ dàng để không có giá trị như bạn nghĩ. Ở đây, chúng ta biết rằng hai giá trị, bất kể chúng là gì, đều giống nhau.


7
@ThomasWeller Đó là một lỗi đã biết trong quá trình triển khai không tuân thủ. Tốt để đề cập đến nó mặc dù!
Cuộc đua nhẹ nhàng trong quỹ đạo

Lúc đầu, tôi nghĩ rằng ngôn ngữ hợp pháp hóa sự khác biệt giữa "giá trị" và "kết quả" sẽ là sai lầm, nhưng sự khác biệt này không bắt buộc phải có nếu không có sự khác biệt bởi ngôn ngữ của C2.2, 7.1.6; C3.3, 7.1.6; C4.2, 7.1.6 hoặc C5.3, 7.1.6 của dự thảo Tiêu chuẩn bạn trích dẫn.
Tháp Eric

@EricTowers Xin lỗi, bạn có thể làm rõ những tài liệu tham khảo đó không? Tôi không tìm thấy những gì bạn đang chỉ vào
Các cuộc đua nhẹ nhàng trong quỹ đạo

@ LightnessRacesBY-SA3.0: C . C2.2 , C3.3 , C4.2C5.3 .
Tháp Eric

@EricTowers Vâng, vẫn không theo dõi bạn. Liên kết đầu tiên của bạn đi đến chỉ mục Phụ lục C (không cho tôi biết bất cứ điều gì). Bốn liên kết tiếp theo của bạn tất cả đi đến [expr]. Nếu tôi bỏ qua các liên kết và tập trung vào các trích dẫn, tôi sẽ bị nhầm lẫn, ví dụ như C.5.3 dường như không giải quyết việc sử dụng thuật ngữ "giá trị" hoặc thuật ngữ "kết quả" (mặc dù nó có sử dụng "kết quả" một lần trong ngữ cảnh tiếng Anh thông thường của nó). Có lẽ bạn có thể mô tả rõ ràng hơn về nơi bạn nghĩ rằng tiêu chuẩn tạo ra sự khác biệt và cung cấp một trích dẫn rõ ràng duy nhất cho điều này xảy ra. Cảm ơn!
Các cuộc đua nhẹ nhàng trong quỹ đạo

3

Có, trong mọi trường hợp (không tính đến các vấn đề NaN và x87), điều này sẽ đúng.

Nếu bạn thực hiện memcmpchúng, bạn sẽ có thể kiểm tra sự bằng nhau trong khi có thể so sánh NaN và sNaN. Điều này cũng sẽ yêu cầu trình biên dịch lấy địa chỉ của biến sẽ ép giá trị thành 32 bit floatthay vì 80 bit. Điều này sẽ loại bỏ các vấn đề x87. Khẳng định thứ hai ở đây dự định không thể hiện rằng ==sẽ không so sánh NaN là đúng:

#include <cmath>
#include <cassert>
#include <cstring>

int main(void)
{
    float x = std::nan("");
    float y = x;
    assert(!std::memcmp(&y, &x, sizeof(float)));
    assert(y == x);
    return 0;
}

Lưu ý rằng nếu NaN có một đại diện bên trong khác (nghĩa là khác nhau), thì memcmpsẽ không so sánh đúng.


1

Trong trường hợp thông thường, nó sẽ đánh giá là đúng. (hoặc tuyên bố khẳng định sẽ không làm gì cả)

Chỉnh sửa :

Theo 'các trường hợp thông thường', ý tôi là loại trừ các tình huống đã nói ở trên (như giá trị NaN và đơn vị dấu phẩy động 80x87) như được chỉ ra bởi những người dùng khác.

Với sự lỗi thời của 8087 chip trong bối cảnh ngày nay, vấn đề này khá cô lập và đối với câu hỏi có thể áp dụng trong tình trạng kiến ​​trúc dấu phẩy động được sử dụng, điều này đúng cho mọi trường hợp ngoại trừ NaNs.

(tham khảo khoảng 8087 - https://home.deec.uc.pt/~jlobo/tc/artofasm/ch14/ch143.htm )

Kudos đến @chtz để tái tạo một ví dụ hay và @kmdreko khi đề cập đến NaN - trước đây họ không biết về họ!


1
Tôi nghĩ rằng nó hoàn toàn có xthể ở trong một thanh ghi dấu phẩy động trong khi yđược tải từ bộ nhớ. Bộ nhớ có thể có độ chính xác thấp hơn một thanh ghi, khiến cho việc so sánh không thành công.
David Schwartz

1
Đó có thể là một trường hợp sai, tôi đã không nghĩ xa đến vậy. (vì OP không cung cấp bất kỳ trường hợp đặc biệt nào, tôi giả sử không có ràng buộc bổ sung nào)
Anirban166

1
Tôi thực sự không hiểu những gì bạn đang nói. Theo tôi hiểu câu hỏi, OP đang hỏi liệu sao chép một float và sau đó kiểm tra sự bình đẳng có được đảm bảo để thành công hay không. Câu trả lời của bạn dường như đang nói "có". Tôi đang hỏi tại sao câu trả lời là không.
David Schwartz

6
Việc chỉnh sửa làm cho câu trả lời này không chính xác. Tiêu chuẩn C ++ yêu cầu việc chuyển đổi giá trị thành độ chính xác vượt quá mức độ chính xác có thể được sử dụng trong các đánh giá biểu thức nhưng có thể không được giữ lại thông qua gán. Nó là không quan trọng cho dù giá trị được giữ trong một thanh ghi hoặc bộ nhớ; tiêu chuẩn C ++ yêu cầu nó, vì mã được viết, một floatgiá trị không có độ chính xác cao.
Eric Postpischil

2
@AProgrammer Cho rằng về mặt lý thuyết, int a=1; int b=a; assert( a==b );một trình biên dịch lỗi (n cực kỳ) có thể gây ra sự khẳng định, tôi nghĩ rằng việc trả lời câu hỏi này liên quan đến trình biên dịch hoạt động chính xác là rất hợp lý (trong khi có thể lưu ý rằng một số phiên bản của một số trình biên dịch có / có -been-biết-để có được điều này sai). Trong điều kiện thực tế, nếu vì một lý do nào đó, trình biên dịch không loại bỏ độ chính xác bổ sung khỏi kết quả của việc gán lưu trữ đăng ký, thì nó nên làm như vậy trước khi sử dụng giá trị đó.
TripeHound

-1

Có, nó sẽ trả về True luôn, trừ khi đó là NaN . Nếu giá trị biến là NaN thì nó luôn trả về Sai !

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.