I know of three functions for checking for equality: ==, ===, and isequal. The documentation comments on subtlety regarding floating point numbers, and there is indeed a difference:

julia> NaN == NaN
false
julia> isequal(NaN, NaN)
true
julia> NaN === NaN
true

Could you explain why they behave this way? I am particularly interested in

Why isequal was designed to behave differently from == for float numbers.

Why NaN === NaN is true. For most other types, I would have naively assumed a === b implies a==b since x===y

Determine whether x and y are identical, in the sense that no program could distinguish them.

according to the documentation.

In terms of my knowledge level, I know floating point numbers are represented as bitstrings with a sign bit, exponent bits, and mantissa bits, but donâ€™t really know much else.

How NaN are handled is outlined in IEEE-754. All comparisons between NaN and any number including itself return false.

=== on the other hand is a different operation that checks that no operation could tell them apart. For mutable structs this is pointer equality and for immutable structs this is bitwise equality.

There are actually several different representations for NaNâ€™s, which are not ===. The best way to check if a value is NaN is with the function isnan

Thanks for pointing me to IEEE-754. Iâ€™ve always thought of == as checking for equality of value, and did not know there was a specification given by IEEE. Now it makes sense that NaN==NaN is false but Nan === Nan.

Do you also happen to know why isequal was designed to return true for two NaN in Julia?

As @WschW notes, there is only one best practice when it comes to determining if one or more values is â€śnot a numberâ€ť (NaN): use isnan(x) or, to determine if two values are NaNs, use e.g. nans(x,y) = isnan(x) && isnan(y) . And, fortunately, isnan(x) runs fast.