Tại sao Double.NaN == Double.NaN trả về sai?


155

Tôi chỉ đang nghiên cứu các câu hỏi OCPJP và tôi đã tìm thấy mã lạ này:

public static void main(String a[]) {
    System.out.println(Double.NaN==Double.NaN);
    System.out.println(Double.NaN!=Double.NaN);
}

Khi tôi chạy mã, tôi nhận được:

false
true

Làm thế nào là đầu ra falsekhi chúng ta so sánh hai thứ trông giống nhau? Có NaNnghĩa là gì?


8
Điều này thật kỳ quái. Vì Double.NaN là cuối cùng tĩnh, nên việc so sánh với == sẽ trả về đúng. +1 cho câu hỏi.
Stephan

2
Điều tương tự cũng đúng ở python:In [1]: NaN==NaN Out[1]: False
tdc

58
Điều này cũng đúng trong tất cả các ngôn ngữ tuân thủ chính xác tiêu chuẩn IEEE 754.
zzzzBov

4
Trực giác: "Xin chào" không phải là một số, đúng (boolean) cũng không phải là một số. NaN! = NaN với cùng lý do "Xin chào"! = Đúng
Kevin

3
@Stephan: Việc so sánh với Double.NaN==Double.NaNthực sự sẽ trả về đúng nếu Double.NaNthuộc loại java.lang.Double. Tuy nhiên, loại của nó là nguyên thủy doublevà các quy tắc vận hành để doubleáp dụng (đòi hỏi sự bất bình đẳng này để tuân thủ với IEEE 754, như được giải thích trong các câu trả lời).
sleske

Câu trả lời:


139

NaN có nghĩa là "Không phải là số".

Đặc tả ngôn ngữ Java (JLS) Phiên bản thứ ba cho biết :

Một hoạt động tràn ra tạo ra một vô cực đã ký, một hoạt động dưới mức tạo ra một giá trị không chuẩn hóa hoặc một số 0 đã ký và một hoạt động không có kết quả xác định về mặt toán học tạo ra NaN. Tất cả các hoạt động số với NaN là toán hạng đều tạo ra NaN. Như đã được mô tả, NaN không có thứ tự, do đó, một hoạt động so sánh số liên quan đến một hoặc hai trả về NaN falsevà bất kỳ !=so sánh nào liên quan đến trả về NaN true, kể cả x!=xkhi nào xlà NaN.


4
@nibot: Chủ yếu là đúng . Bất kỳ so sánh với một float phù hợp với IEEE sẽ tạo ra false. Vì vậy, tiêu chuẩn đó khác với Java ở chỗ mà IEEE yêu cầu điều đó (NAN != NAN) == false.
Drew Dormann

2
Mở hộp Pandora này - bạn thấy "IEEE yêu cầu (NAN! = NAN) == false" ở đâu?
Giám sát viên

62

NaN theo định nghĩa không bằng bất kỳ số nào kể cả NaN. Đây là một phần của tiêu chuẩn IEEE 754 và được CPU / FPU triển khai. Nó không phải là thứ mà JVM phải thêm bất kỳ logic nào để hỗ trợ.

http://en.wikipedia.org/wiki/NaN

So sánh với NaN luôn trả về kết quả không có thứ tự ngay cả khi so sánh với chính nó. ... Các vị từ đẳng thức và bất đẳng thức không có tín hiệu nên x = x trả về false có thể được sử dụng để kiểm tra nếu x là NaN yên tĩnh.

Java coi tất cả NaN là NaN yên tĩnh.


1
Nó được CPU triển khai hay nó có dây cứng trong JVM như đề cập của Bohemian không?
Naweed Chougle

3
JVM phải gọi bất cứ điều gì sẽ thực hiện nó một cách chính xác. Trên PC, CPU thực hiện mọi công việc như vậy. Trên một máy không có hỗ trợ này, JVM phải thực hiện nó. (Tôi không biết về bất kỳ máy nào như vậy)
Peter Lawrey

Ngày trước khi 8087 là một tùy chọn, thư viện C chứa trình giả lập FP. Các chương trình như JVM sẽ không phải lo lắng về điều đó.
Hầu tước Lorne

49

Tại sao logic đó

NaNcó nghĩa là Not a Number. Những gì không phải là một số? Bất cứ điều gì. Bạn có thể có bất cứ thứ gì ở một bên và bất cứ thứ gì ở phía bên kia, vì vậy không có gì đảm bảo rằng cả hai đều bằng nhau. NaNđược tính toán với Double.longBitsToDouble(0x7ff8000000000000L)và như bạn có thể thấy trong tài liệu về longBitsToDouble:

Nếu đối số là bất kỳ giá trị nào trong phạm vi 0x7ff0000000000001Lthông qua 0x7fffffffffffffffLhoặc trong phạm vi0xfff0000000000001L thông qua 0xffffffffffffffffL, kết quả là a NaN.

Ngoài ra, NaNđược xử lý logic bên trong API.


Tài liệu

/** 
 * A constant holding a Not-a-Number (NaN) value of type
 * {@code double}. It is equivalent to the value returned by
 * {@code Double.longBitsToDouble(0x7ff8000000000000L)}.
 */
public static final double NaN = 0.0d / 0.0;

Nhân tiện, NaN được kiểm tra như mẫu mã của bạn:

/**
 * Returns {@code true} if the specified number is a
 * Not-a-Number (NaN) value, {@code false} otherwise.
 *
 * @param   v   the value to be tested.
 * @return  {@code true} if the value of the argument is NaN;
 *          {@code false} otherwise.
 */
static public boolean isNaN(double v) {
    return (v != v);
}

Giải pháp

Những gì bạn có thể làm là sử dụng compare/compareTo :

Double.NaNđược phương pháp này coi là bằng chính nó và lớn hơn tất cả các doublegiá trị khác (bao gồm Double.POSITIVE_INFINITY).

Double.compare(Double.NaN, Double.NaN);
Double.NaN.compareTo(Double.NaN);

Hoặc là, equals :

Nếu thisargumentcả hai đại diện Double.NaN, thì equalsphương thức trả về true, mặc dù Double.NaN==Double.NaNcó giá trị false.

Double.NaN.equals(Double.NaN);

Bạn có biết trường hợp nào NaN != NaNsai khi làm chương trình phức tạp hơn NaN != NaNlà đúng không? Tôi biết IEEE đã đưa ra quyết định từ lâu, nhưng từ góc độ thực tế, tôi chưa bao giờ thấy trường hợp nào hữu ích. Nếu một hoạt động được cho là chạy cho đến khi các lần lặp liên tiếp mang lại kết quả tương tự, việc có hai lần lặp liên tiếp mang lại NaN sẽ được "phát hiện" một cách tự nhiên như một điều kiện thoát không phải là hành vi đó.
supercat

@supercat Làm thế nào bạn có thể nói rằng hai số không ngẫu nhiên là tự nhiên bằng nhau? Hay nói, nguyên thủy bằng nhau? Hãy nghĩ về NaN như một ví dụ, không phải là một cái gì đó nguyên thủy. Mỗi kết quả bất thường khác nhau là một trường hợp khác nhau của một cái gì đó lạ và ngay cả khi cả hai nên đại diện giống nhau, sử dụng == cho các trường hợp khác nhau phải trả về false. Mặt khác, khi sử dụng bằng nó có thể được xử lý đúng như bạn dự định. [ docs.oracle.com/javase/7/docs/api/java/lang/ triệt
falsarella

@falsarella: Vấn đề không phải là liệu hai số ngẫu nhiên có được coi là "chắc chắn bằng nhau" hay không, mà là trong trường hợp nào hữu ích khi có bất kỳ số nào được so sánh là "chắc chắn không bằng nhau" với chính nó. Nếu một người đang cố gắng tính giới hạn của f(f(f...f(x))), và người ta tìm thấy một y=f[n](x)số nsao cho kết quả f(y)không thể phân biệt được y, thì ysẽ không thể phân biệt được với kết quả của bất kỳ - lồng nhau sâu hơn f(f(f(...f(y))). Thậm chí nếu ai muốn NaN==NaNlà sai, có Nan!=Nan cũng là sai lầm sẽ ít "đáng ngạc nhiên" vì phải x!=xlà sự thật đối với một số x.
supercat

1
@falsarella: Tôi tin rằng loại Double.NaNnày không phải Double, nhưng double, vì vậy câu hỏi là một trong những liên quan đến hành vi của double. Mặc dù tồn tại các hàm có thể kiểm tra mối quan hệ tương đương liên quan đến doublecác giá trị, nhưng câu trả lời hấp dẫn duy nhất tôi biết về "tại sao" (là một phần của câu hỏi ban đầu) là "vì một số người tại IEEE không nghĩ rằng kiểm tra bình đẳng nên định nghĩa một mối quan hệ tương đương ". BTW, có cách nào thành ngữ ngắn gọn để kiểm tra xytương đương chỉ sử dụng các toán tử nguyên thủy không? Tất cả các công thức tôi biết là khá cồng kềnh.
supercat

1
câu trả lời tốt nhất và đơn giản. Cảm ơn bạn
Tarun Nagpal

16

Nó có thể không phải là một câu trả lời trực tiếp cho câu hỏi. Nhưng nếu bạn muốn kiểm tra xem có thứ gì đó bằng với Double.NaNbạn hay không thì nên sử dụng cái này:

double d = Double.NaN
Double.isNaN(d);

Điều này sẽ trở lại true


6

Các javadoc cho Double.NaN nói lên tất cả:

Một hằng số giữ giá trị Không phải là Số (NaN) của loại double. Nó tương đương với giá trị được trả về Double.longBitsToDouble(0x7ff8000000000000L).

Thật thú vị, nguồn để Doubleđịnh nghĩa NaNnhư vậy:

public static final double NaN = 0.0d / 0.0;

Hành vi đặc biệt mà bạn mô tả là cứng cáp vào JVM.


5
Nó có dây cứng trong JVM không, hay nó được CPU triển khai như Peter đề cập?
Naweed Chougle

4

theo tiêu chuẩn của IEEE về số học dấu phẩy động cho các số Chính xác kép,

Biểu diễn tiêu chuẩn điểm nổi chính xác kép của IEEE yêu cầu một từ 64 bit, có thể được biểu diễn dưới dạng được đánh số từ 0 đến 63, từ trái sang phải

nhập mô tả hình ảnh ở đây Ở đâu,

S: Sign  1 bit
E: Exponent  11 bits
F: Fraction  52 bits 

Nếu E=2047(tất cả E1) và Flà khác không, thì V=NaN("Không phải là số")

Nghĩa là,

Nếu tất cả các Ebit là 1 và nếu có bất kỳ bit nào khác không Fthì số đó làNaN .

do đó, trong số những người khác, tất cả các số sau đây là NaN,

0 11111111 0000000000000000010000000000000000000000000000000000 = NaN
1 11111111 0000010000000000010001000000000000001000000000000000 = NaN
1 11111111 0000010000011000010001000000000000001000000000000000 = NaN

Đặc biệt, bạn không thể kiểm tra

if (x == Double.NaN) 

để kiểm tra xem một kết quả cụ thể có bằng không Double.NaN, bởi vì tất cả các giá trị không phải là một số giá trị được coi là khác biệt. Tuy nhiên, bạn có thể sử dụng Double.isNaNphương pháp:

if (Double.isNaN(x)) // check whether x is "not a number"

3

NaN là một giá trị đặc biệt biểu thị "không phải là số"; đó là kết quả của một số phép toán số học không hợp lệ, chẳng hạn như sqrt(-1), và có thuộc tính (đôi khi gây khó chịu) NaN != NaN.


2

Không phải là một số đại diện cho kết quả của các hoạt động mà kết quả không thể biểu thị bằng một số. Hoạt động nổi tiếng nhất là 0/0, mà kết quả không được biết đến.

Vì lý do này, NaN không bằng bất cứ thứ gì (bao gồm các giá trị không phải là số khác). Để biết thêm thông tin, chỉ cần kiểm tra trang wikipedia: http://en.wikipedia.org/wiki/NaN


-1: Nó không đại diện cho kết quả của 0/0. 0/0luôn luôn là NaN, nhưng NaN có thể là kết quả của các hoạt động khác - chẳng hạn như 2+NaN: an operation that has no mathematically definite result produces NaN, theo câu trả lời của @AdrianMitev
ANeves

Thật vậy, NaN là viết tắt của "Không phải là số" và đó là kết quả của tất cả các hoạt động có kết quả là giá trị không xác định hoặc không thể biểu thị được. Hoạt động nổi tiếng và phổ biến nhất là 0/0, nhưng rõ ràng có hàng tấn hoạt động khác có kết quả tương tự. Tôi đồng ý rằng câu trả lời của tôi có thể được cải thiện, nhưng tôi không đồng ý với -1 ... Tôi chỉ kiểm tra xem wikipedia cũng sử dụng các thao tác 0/0 làm ví dụ đầu tiên về hoạt động với kết quả NaN ( en.wikipedia.org/wiki/ NaN ).
Matteo

Ngoài ra, đây là trong nguồn Java cho Double: double static static NaN = 0,0d / 0,0;
Guillaume

1
@Matteo +0, bây giờ báo cáo sai đã biến mất. Và -1 hoặc +1 của tôi không phải để bạn đồng ý hay không đồng ý; nhưng thật tốt khi để lại một bình luận với -1, để tác giả có thể hiểu tại sao câu trả lời của anh ta được coi là không đáng tin - và thay đổi nó, nếu anh ta muốn như vậy.
ANeves

@Guillaume nếu nhận xét đó có ý nghĩa với tôi, vui lòng viết lại nó: Tôi không hiểu nó.
ANeves

0

Theo liên kết này , nó có nhiều tình huống khác nhau và khó nhớ. Đây là cách tôi nhớ và phân biệt chúng. NaNcó nghĩa là "không xác định về mặt toán học", ví dụ: "kết quả của 0 chia cho 0 là không xác định" và vì nó không được xác định, do đó "so sánh liên quan đến không xác định tất nhiên là không xác định". Bên cạnh đó, nó hoạt động giống như cơ sở toán học. Mặt khác, cả vô hạn dương và âm đều được xác định trước và dứt khoát, ví dụ "vô hạn dương hoặc âm lớn được xác định rõ về mặt toán học".

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.