Tại sao NaN - NaN == 0,0 với Trình biên dịch Intel C ++?


300

Người ta biết rằng NaNs lan truyền trong số học, nhưng tôi không thể tìm thấy bất kỳ cuộc biểu tình nào, vì vậy tôi đã viết một bài kiểm tra nhỏ:

#include <limits>
#include <cstdio>

int main(int argc, char* argv[]) {
    float qNaN = std::numeric_limits<float>::quiet_NaN();

    float neg = -qNaN;

    float sub1 = 6.0f - qNaN;
    float sub2 = qNaN - 6.0f;
    float sub3 = qNaN - qNaN;

    float add1 = 6.0f + qNaN;
    float add2 = qNaN + qNaN;

    float div1 = 6.0f / qNaN;
    float div2 = qNaN / 6.0f;
    float div3 = qNaN / qNaN;

    float mul1 = 6.0f * qNaN;
    float mul2 = qNaN * qNaN;

    printf(
        "neg: %f\nsub: %f %f %f\nadd: %f %f\ndiv: %f %f %f\nmul: %f %f\n",
        neg, sub1,sub2,sub3, add1,add2, div1,div2,div3, mul1,mul2
    );

    return 0;
}

Ví dụ ( chạy trực tiếp tại đây ) tạo ra về cơ bản những gì tôi mong đợi (tiêu cực hơi kỳ lạ, nhưng nó có ý nghĩa):

neg: -nan
sub: nan nan nan
add: nan nan
div: nan nan nan
mul: nan nan

MSVC 2015 sản xuất một cái gì đó tương tự. Tuy nhiên, Intel C ++ 15 tạo ra:

neg: -nan(ind)
sub: nan nan 0.000000
add: nan nan
div: nan nan nan
mul: nan nan

Cụ thể qNaN - qNaN == 0.0,.

Điều này ... không thể đúng, phải không? Các tiêu chuẩn liên quan (ISO C, ISO C ++, IEEE 754) nói gì về điều này và tại sao có sự khác biệt về hành vi giữa các trình biên dịch?


18
Javascript và Python (numpy) không có hành vi này. Nan-NaNNaN. Perl và Scala cũng hành xử tương tự.
Paul

33
Có lẽ bạn đã kích hoạt tối ưu hóa toán học không an toàn (tương đương với -ffast-mathtrên gcc)?
Matteo Italia

5
@nm: Không đúng. Phụ lục F, là tùy chọn nhưng mang tính quy phạm khi được hỗ trợ và cần phải có hành vi dấu phẩy động được chỉ định ở tất cả , về cơ bản kết hợp với IEEE 754 vào C.
R .. GitHub DỪNG GIÚP ICE

5
Nếu bạn muốn hỏi về tiêu chuẩn IEEE 754, hãy đề cập đến nó ở đâu đó trong câu hỏi.
n. 'đại từ' m.

68
Tôi chắc chắn câu hỏi này là về JavaScript từ tiêu đề.
MikeTheLiar

Câu trả lời:


300

Việc xử lý dấu phẩy động mặc định trong trình biên dịch Intel C ++ là /fp:fastxử lý NaNkhông an toàn ( ví dụ, kết quả NaN == NaNtruechẳng hạn). Hãy thử chỉ định /fp:stricthoặc /fp:precisevà xem nếu điều đó giúp.


15
Tôi chỉ đang cố gắng bản thân mình. Thật vậy, chỉ định chính xác hoặc nghiêm ngặt khắc phục vấn đề.
imallett

67
Tôi muốn chứng thực quyết định của Intel về mặc định /fp:fast: nếu bạn muốn thứ gì đó an toàn , có lẽ tốt hơn hết bạn nên tránh sử dụng NaN ở vị trí đầu tiên và thường không sử dụng ==với số dấu phẩy động. Dựa vào ngữ nghĩa kỳ lạ mà IEEE754 gán cho NaN đang yêu cầu sự cố.
rẽ trái

10
@leftaroundabout: Bạn thấy điều gì kỳ lạ về NaN, ngoài quyết định khủng khiếp của IMHO là có NaN! = NaN trở lại đúng không?
supercat

21
NaN có công dụng quan trọng - chúng có thể phát hiện các tình huống đặc biệt mà không yêu cầu kiểm tra sau mỗi lần tính toán. Không phải mọi nhà phát triển dấu phẩy động đều cần chúng nhưng đừng loại bỏ chúng.
Bruce Dawson

6
@supercat Vì tò mò, bạn có đồng ý với quyết định NaN==NaNquay lại falsekhông?
Kyle Strand

53

Điều này . . . không thể đúng, phải không? Câu hỏi của tôi: các tiêu chuẩn liên quan (ISO C, ISO C ++, IEEE 754) nói gì về điều này?

Petr Abdulin đã trả lời tại sao trình biên dịch đưa ra 0.0câu trả lời.

Đây là những gì IEEE-754: 2008 nói:

(6.2 Hoạt động với NaN) "[...] Đối với hoạt động có đầu vào NaN yên tĩnh, ngoài hoạt động tối đa và tối thiểu, nếu kết quả dấu phẩy động được cung cấp, kết quả sẽ là một NaN yên tĩnh phải là một trong những NaN đầu vào. "

Vì vậy, kết quả hợp lệ duy nhất cho phép trừ của hai toán hạng NaN yên tĩnh là một NaN yên tĩnh; bất kỳ kết quả khác là không hợp lệ.

Tiêu chuẩn C nói:

(C11, F.9.2 Các phép biến đổi biểu thức p1) "[...]

x - x → 0. 0 "Các biểu thức x - x và 0. 0 không tương đương nếu x là NaN hoặc vô hạn"

(ở đây NaN biểu thị một NaN yên tĩnh theo F.2.1p1 "Thông số kỹ thuật này không xác định hành vi của tín hiệu NaN. Nó thường sử dụng thuật ngữ NaN để biểu thị NaN yên tĩnh")


20

Vì tôi thấy câu trả lời bao hàm sự tuân thủ các tiêu chuẩn của trình biên dịch của Intel và không ai khác đề cập đến điều này, tôi sẽ chỉ ra rằng cả GCC và Clang đều có chế độ làm việc tương tự nhau. Hành vi mặc định của họ là tuân thủ theo chuẩn IEEE -

$ g++ -O2 test.cc && ./a.out 
neg: -nan
sub: nan nan nan
add: nan nan
div: nan nan nan
mul: nan nan

$ clang++ -O2 test.cc && ./a.out 
neg: -nan
sub: -nan nan nan
add: nan nan
div: nan nan nan
mul: nan nan

- nhưng nếu bạn yêu cầu tốc độ với chi phí chính xác, bạn sẽ nhận được những gì bạn yêu cầu -

$ g++ -O2 -ffast-math test.cc && ./a.out 
neg: -nan
sub: nan nan 0.000000
add: nan nan
div: nan nan 1.000000
mul: nan nan

$ clang++ -O2 -ffast-math test.cc && ./a.out 
neg: -nan
sub: -nan nan 0.000000
add: nan nan
div: nan nan nan
mul: nan nan

Tôi nghĩ hoàn toàn công bằng khi chỉ trích sự lựa chọn mặc định của ICC , nhưng tôi sẽ không đọc toàn bộ cuộc chiến Unix về quyết định đó.


Lưu ý rằng với -ffast-math, gcckhông tuân thủ ISO 9899: 2011 đối với số học dấu phẩy động nữa.
fuz

1
@FUZxxl Vâng, vấn đề là cả hai trình biên dịch đều có chế độ dấu phẩy động không tuân thủ, chỉ là icc mặc định cho chế độ đó và gcc thì không.
zwol

4
Chỉ cần ném nhiên liệu vào lửa, tôi thực sự thích lựa chọn của Intel là bật toán học nhanh theo mặc định. Toàn bộ quan điểm của việc sử dụng phao là để có được thông lượng cao.
Navin
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.