0/0
returns NaN
NaN*false
returns 0.0
(0/0)*false
returns -0.0
Isn’t this an inconsistency of NaN?
Also:
julia> (NaN*(0/0))*false
0.0
but:
julia> ((0/0)*NaN)*false
-0.0
It appears that these are two different bit values although they both print as NaN
julia> reinterpret(UInt,0/0)
0xfff8000000000000
julia> reinterpret(UInt,NaN)
0x7ff8000000000000
julia> 0/0 == NaN
false
they differ only by a single bit, presumably the sign bit
Ok, but:
julia> 0.0 == -0.0
true
though:
julia> 0.0 === -0.0
false
so at least
julia> (0/0)*false == NaN*false
true
That’s correct, they can’t be egal (===
) because the sign bit is different:
julia> reinterpret(UInt, 0.0)
0x0000000000000000
julia> reinterpret(UInt, -0.0)
0x8000000000000000
or
julia> bitstring(0.0)
"0000000000000000000000000000000000000000000000000000000000000000"
julia> bitstring(-0.0)
"1000000000000000000000000000000000000000000000000000000000000000"
Ok, and what about:
julia> (NaN*(0/0))*false
0.0
but:
julia> ((0/0)*NaN)*false
-0.0
In IEEE-754, the IEEE standard for floating point arithmetic, which Julia and most other languages use, NaN
s are encoded with the exponent field filled with ones. According to the standard, the sign bit does not matter for determining NaN
ness, and the sign bit is precisely what’s different between 0/0
and NaN
. I’m not sure exactly why 0/0
results in a different sign bit, but it could well just be an implementation detail. Next, there’s the multiplication with the Bool
false
, which (eventually) hits this method:
The copysign
is what causes the sign bit of the NaN
to appear in the final result. I’m not sure whether that’s intended. I suppose it is a little weird that information that’s supposed to be (mostly) immaterial has an observable effect here. Then again, it seems harmless.
Why is why the way to determine NaN
ness is to use isnan
and not any number of consecutive =
's. You’re not supposed to do 0/0 == NaN
to check if the lhs is NaN
.
or use haskey
julia> haskey(Dict(NaN => nothing), 0/0)
true
There are several things to unpack in this post.
-
false*NaN
is special cased to makefalse
“strong”, see the implementation -
but the
copysign
method forNaN
s is a bit misleading, asNaN
s are not supposed to have a sign:julia> sign(0/0) NaN julia> copysign(1.0, 0/0) -1.0
I don’t know the arcane details of IEEE 754 about whether this is the intended behavior. In any case, it is mostly innocuous.
-
(NaN*(0/0))*false
is the same phenomenon, except the sign bit comes from the first operand.
It’s what the hardware does, iirc.
Or rather isequal
(which is what haskey
uses): isequal(NaN, 0/0) == true
.
Whoa there. I don’t know if these are officially bugs but they are at odds with the IEEE 754 spec, which reads in part:
For an operation with quiet NaN inputs, other than maximum and minimum operations, if a floating-point result is to be delivered the result shall be a quiet NaN which should be one of the input NaNs.
See here: http://irem.univ-reunion.fr/IMG/pdf/ieee-754-2008.pdf
I understand why people usually want false*x to be falsy but NaN is falsy enough and more correct than 0.0 for false*NaN.
I agree. But since this has been in the language for a while, I think a lot of programmers (myself included) use false*x
on uninitialized data and expect to get rid of NaN
s that way.
I think it would be nice if Julia had a “strong zero” in a type of its own. I even wrote a package implementing that a while back (though I never registered it or upgraded it to be 1.0 compatible.)
I think the argument is that IEEE-754 does not pertain to operations involving boolean inputs. I’m not picking sides on whether or not the current behavior of various operations with mixed floating point number and boolean inputs is ‘right’, but it’s not a clear-cut case.