-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
should isequal(0.0, -0.0) == true? #18485
Comments
Does it help or hinder to consider that in fact isequal might be two different ideas? Consider:
It is perhaps unfortunate that multiplication propagates the negative for reals: 0.0 * -0.0 evaluates to -0.0; it is ok for integers. With respect to 0.0 and -0.0 it is a similar problem to that of "is noon AM or PM?" In fact it is neither, however we often label noon pm because 11:59AM,12:00AM,12:01PM makes less sense than 12:00PM in that context. |
I like this question. # v0.5
nan1 = Inf/Inf; nan1 = -0.0*Inf; pz = 0.0; nz= -0.0;
nan1==nan2, nan1===nan2, isequal(nan1,nan2)
# (false, true, true)
pz==nz, pz===nz, isequal(pz,nz)
# (true, false, false)
# for a.b =Nan, NaN and for a,b = 0.0, -0.0
# (==)(a,b) = !(===)(a,b) so a == b iff a and b differ
with (0.0 == -0.0) === true, That is reason-about-able for entities that are NotNumber (excluded from numerosity). I favor that Julia specify isequal( 0.0, -0.0 ) is true. |
@JeffreySarnoff Do you then agree that sorting of numbers doesn't distinguish between positive and negative zero any more, and that hashing returns the same value, and that dictionaries can hold only one key for zero, etc.? |
@eschnett no. branch-cut sanity carries utility and pervades reliability more than does zeroing the zeros pz = 0.0; nz = -pz;
showcompact( (atan2(nz,nz), atan2(nz, pz), atan2(pz, pz), atan2(pz, nz)) )
# ( -3.14159,-0.0, 0.0, 3.14159 ) object_id( pz ) != object_id( nz ) # now, and my forward preference |
@eschnett One cannot sort handedness nor parity, one may group like handednesses and same parity. This is not a question: Which color precedes? |
White. |
The lower White or the left White? |
That's a tricky one. Will have to come back to you on that. |
@JeffreySarnoff Did you answer my question? Let me rephrase things:
|
@eschnett alright Ought isequal( 0.0, -0.0 ) be true? Yes. Must object_id( 0.0 ) differ from object_id( -0.0 )? Yes. Must some Dict dictionaries hold different values for keys 0.0 and -0.0? Yes.
Should hash(0.0) == hash(-0.0)? Yes. |
That's fine for |
@JeffBezanson you intuited my intent. As long as ObjectIdDict is available, the Must some Dict dictionaries statement is satisfied, and happiness suffuses logic. |
|
Here's a crazier but more general idea: might it be possible to rename the current immutable HashDict{K,V,Hft,Eft} <: Associative{K,V}
hash::Hft
isequal::Eft
... # as present
end then define typealias Dict{K,V} HashDict{K,V,typeof(hash),typeof(isequal)} and thus allow users to supply their own hash and equality functions, kind of like how C++ does it? |
Yes, it's possible and works like you surmised. Here's a complete demo implementation I had toyed with a bit a couple months ago: fce28b4 |
I don't really want to do that – I think it's a case of serious over-parameterization of the type. If you want to use custom hashing for your dict, you should probably call a transformation function on your keys before inserting them; it's much simpler and easier to reason about. It also doesn't address this problem since we still need to provide an |
One parameter could be removed by keeping only the hash function, and computing the |
Ok, but this is totally tangential since however Dict works, we still need to design the way the |
@StefanKarpinski Point understood. The |
Also, if things as different as julia> isequal(0, 0.0)
true
julia> isequal(0, -0.0)
false |
How so? |
Perhaps it's an exaggeration to say that |
Consider that (-0.0)+(-0.0)-(-0.0)==0.0 To me this indicates that -0.0 really should equal 0.0, as otherwise addition that really should be commutative isn't. I would similarly suggest that hash(-0.0)==hash(0.0) and that dicts should treat them the same way. |
My current thinking is that we should not make this change. Here's a run-down:
|
That is arguably handled by |
My point is just that given how many seeming tautologies applied to -0.0 result in 0.0 could cause some really confusing results (although I guess that mainly is because using floats as dict keys is an absolutely horrible idea) |
Nevertheless, it is still confusing that |
Yes, I can kind of see that, but once you accept that |
but consider that isequal(-0, -0.0) is false. |
But |
I like this question.
Yes, and that is a [meaningful operational] difference without a [measure theoretic] distinction. That they are different hither and the same yon is meaningfuller.
Reading that standing on my head,: the branch cuts and the multiplictive resolution of odd signedness are handled for almost all applications that are not writing a floating point math library. The likelihood is members of the Julia community and a user of a Julia products for their own purposes consider them an incomplete sameness rather than as not completely different. Relying on the coherently consistent branch cuts that are availble with 0.0, -0.0 requires onlty that !(0.0 === -0.0), irrespective of the truth valueisequal( 0.0 , -0.0) yeilds. As one is of object non-identitty and iesequal(0.0, -0.) is is of the inststinguishability of lattice-free measures zero, they need [qua ought] not agree..
We are in agreement. |
Yes, since otherwise if you change from a hash-based dictionary data structure to a tree-based one, you'll get different behavior, which is awful. Not having
Lots of languages only have a few names for their orderings. But they usually have a lot of implicit orderings created by the rampant inconsistencies between the different orderings that they actually exhibit. There's a reason that equality is the first place to look for "wat" behavior in languages. Given @JeffBezanson's arguments and the fact that no clever solutions to the sorting issues raised by |
That argument makes sense to me. |
Actually, the sorting issue makes some sense to me as well. import Base: unbox, slt_int, eq_float, isless, isequal
for (T,I) in [(:Float64, :Int64),
(:Float32, :Int32),
(:Float16, :Int16)]
@eval begin
isequal(a::$T, b::$T) = eq_float(unbox($T,a), unbox($T,b))
function isless(a::$T, b::$T)
ia = reinterpret($I, a)
ib = reinterpret($I, b)
ifelse( signbit(ia) & signbit(ib),
slt_int(ib,ia), slt_int(ia,ib) )
end
end
end
floatvec = [-Inf,-100.0,-1.0,-0.5,-0.0,0.0,0.5,1.0,100.0,Inf];
floatvec == sort(floatvec) # true
sort([NaN, Inf, -Inf])' # [-Inf, Inf, NaN] (as now)
issorted([ -0.0, 0.0 ]), issorted([ 0.0, -0.0 ]) # (true, false)
-0.0 === 0.0, 0.0 === -0.0 # (false, false)
-0.0 == 0.0, 0.0 == -0.0 # (true, true)
-0.0 < 0.0, 0.0 < -0.0 # (false, false)
-0.0 <= 0.0, 0.0 <= -0.0 # (true, true)
isequal(-0.0, 0.0), isequal(0.0, -0.0) # (true, true)
isless(-0.0, 0.0), isless(0.0, -0.0) # (true, false) that's all -- thanks for the active discussion |
Seems like this is resolved in favor of preserving the current behavior |
This seems resolved, and (for once) conveniently in a way that requires no action. |
ok. for my own edification: what was the drawback to the implementation I On Thu, Oct 27, 2016 at 1:28 PM, Stefan Karpinski [email protected]
|
That's a redefinition of what sorting means, not an implementation of stable sorting. |
Related discussion: https://discourse.julialang.org/t/possible-bug-in-unique-set/5371. |
For newcomers who may be curious about this issue and finding this discussion, would it be accurate to say that, after applying type promotion rules:
? |
Roughly, except that |
Came up in #9381 but deserves its own issue: it's been proposed that positive and negative zero should hash and compare as equal, but that has an unfortunate interaction with sorting and stability.
The text was updated successfully, but these errors were encountered: