Như @Angew đã chỉ ra , !=
nhà điều hành cần cùng một loại ở cả hai bên.
(float)i != i
kết quả là thúc đẩy RHS nổi, vì vậy chúng tôi có (float)i != (float)i
.
g ++ cũng tạo ra một vòng lặp vô hạn, nhưng nó không tối ưu hóa công việc từ bên trong nó. Bạn có thể thấy nó chuyển đổi int-> float với cvtsi2ss
và does ucomiss xmm0,xmm0
để so sánh (float)i
với chính nó. (Đó là manh mối đầu tiên của bạn rằng nguồn C ++ của bạn không có nghĩa là bạn nghĩ nó đã làm như câu trả lời của @ Angew giải thích.)
x != x
chỉ đúng khi nó "không có thứ tự" vì x
là NaN. ( INFINITY
so sánh bằng chính nó trong toán học IEEE, nhưng NaN thì không. NAN == NAN
là false, NAN != NAN
là true).
gcc7.4 trở lên tối ưu hóa chính xác mã của bạn thành jnp
nhánh vòng lặp ( https://godbolt.org/z/fyOhW1 ): tiếp tục lặp lại miễn là các toán hạng x != x
không phải là NaN. (gcc8 trở lên cũng kiểm tra je
để thoát khỏi vòng lặp, không thể tối ưu hóa dựa trên thực tế là nó sẽ luôn đúng với bất kỳ đầu vào không phải NaN nào). x86 FP so sánh PF đã đặt trên không có thứ tự.
Và BTW, điều đó có nghĩa là tối ưu hóa của clang cũng an toàn : nó chỉ cần CSE (float)i != (implicit conversion to float)i
giống nhau và chứng minh rằng i -> float
không bao giờ là NaN cho phạm vi có thể int
.
(Mặc dù cho rằng vòng lặp này sẽ chạm vào UB tràn có dấu, nó được phép phát ra theo nghĩa đen bất kỳ asm nào mà nó muốn, bao gồm lệnh ud2
bất hợp pháp hoặc một vòng lặp vô hạn trống bất kể thân vòng thực sự là gì.) Nhưng bỏ qua UB tràn có dấu. , tối ưu hóa này vẫn hợp pháp 100%.
GCC không thể tối ưu hóa phần nội dung vòng lặp ngay cả -fwrapv
khi làm cho phần tràn số nguyên có dấu được xác định rõ ràng (dưới dạng bao bọc bổ sung của 2). https://godbolt.org/z/t9A8t_
Ngay cả việc bật -fno-trapping-math
cũng không giúp được gì. ( Thật không may , mặc định của GCC là cho phép
-ftrapping-math
mặc dù việc triển khai nó bị hỏng / lỗi .) Int-> chuyển đổi float có thể gây ra ngoại lệ không chính xác FP (đối với các số quá lớn để được thể hiện chính xác), vì vậy, với các ngoại lệ có thể được hiển thị, bạn không nên tối ưu hóa phần thân vòng lặp. (Vì chuyển đổi 16777217
thành float có thể có tác dụng phụ có thể quan sát được nếu ngoại lệ không chính xác được hiển thị.)
Nhưng với -O3 -fwrapv -fno-trapping-math
, việc tối ưu hóa bị bỏ lỡ 100% là không biên dịch nó thành một vòng lặp vô hạn trống. Nếu không #pragma STDC FENV_ACCESS ON
, trạng thái của các cờ dính ghi lại các ngoại lệ FP bị che không phải là một tác dụng phụ có thể quan sát được của mã. Không int
-> float
chuyển đổi có thể dẫn đến NaN, vì vậy x != x
không thể đúng.
Tất cả các trình biên dịch này đều đang tối ưu hóa cho việc triển khai C ++ sử dụng IEEE 754 đơn chính xác (binary32) float
và 32 bit int
.
Các bugfixed(int)(float)i != i
vòng lặp sẽ có UB trên C ++ triển khai với hẹp 16-bit int
và / hoặc rộng hơn float
, bởi vì bạn muốn nhấn ký-integer overflow UB trước khi đến số nguyên đầu tiên đó là không chính xác biểu diễn bằng float
.
Nhưng UB dưới một tập hợp các lựa chọn do triển khai xác định khác không có bất kỳ hậu quả tiêu cực nào khi biên dịch cho một triển khai như gcc hoặc clang với x86-64 System V ABI.
BTW, bạn có thể tính toán tĩnh kết quả của vòng lặp này từ FLT_RADIX
và FLT_MANT_DIG
, được định nghĩa trong <climits>
. Hoặc ít nhất bạn có thể về lý thuyết, nếu float
thực sự phù hợp với mô hình của IEEE float hơn là một số loại biểu diễn số thực khác như Posit / unum.
Tôi không chắc tiêu chuẩn ISO C ++ float
xác định được bao nhiêu về hành vi và liệu một định dạng không dựa trên các trường mũ và nghĩa có độ rộng cố định có tuân thủ tiêu chuẩn hay không.
Trong các bình luận:
@geza Tôi muốn nghe con số kết quả!
@nada: đó là 16777216
Bạn có tuyên bố rằng bạn có vòng lặp này để in / trả lại 16777216
không?
Cập nhật: vì bình luận đó đã bị xóa, tôi nghĩ là không. Có thể OP chỉ trích dẫn float
trước số nguyên đầu tiên không thể được biểu diễn chính xác dưới dạng 32-bit float
. https://en.wikipedia.org/wiki/Single-pre khít_floating-point_format#Pre precision_limits_on_integer_values tức là những gì họ hy vọng sẽ xác minh với mã lỗi này.
Tất nhiên, phiên bản được sửa lỗi sẽ in ra 16777217
, số nguyên đầu tiên không thể đại diện chính xác thay vì giá trị trước đó.
(Tất cả các giá trị float cao hơn đều là số nguyên chính xác, nhưng chúng là bội số của 2, sau đó 4, rồi 8, v.v. cho các giá trị lũy thừa cao hơn ý nghĩa và độ rộng. Nhiều giá trị số nguyên cao hơn có thể được biểu diễn, nhưng 1 đơn vị ở vị trí cuối cùng (của ý nghĩa và) lớn hơn 1 nên chúng không phải là số nguyên liền kề. Số hữu hạn lớn nhất float
chỉ dưới 2 ^ 128, quá lớn so với số chẵn int64_t
.)
Nếu bất kỳ trình biên dịch nào thoát khỏi vòng lặp ban đầu và in ra, đó sẽ là lỗi trình biên dịch.