What is the difference between ==
and ===
comparison operators in Julia?
== is the abstract equality comparison operator and === is the strict equality comparison operator. They both are ways to check for equality. They check if something on the left is equal to something on the right.
The strict equality operator ( === ) checks whether its two operands are equal, returning a Boolean result. Unlike the equality operator, the strict equality operator always considers operands of different types to be different.
== === = in JavaScript is used for assigning values to a variable. == in JavaScript is used for comparing two variables, but it ignores the datatype of variable. === is used for comparing two variables, but this operator also checks datatype and compares two values.
Operators in Julia are the mathematical symbols that are used to perform operations on variables and values. These symbols are used to carry out arithmetic and logical computations. Variables on which the operators perform operations are termed as Operands.
@ChrisRackauckas's answer is accurate as far as it goes – i.e. for mutable objects. There's a bit more to the issue than that, however, so I'll elaborate a bit here.
The ===
operator (an alias for the is
function) implements Henry Baker's EGAL predicate [1, 2]: x === y
is true when two objects are programmatically indistinguishable – i.e. you cannot write code that demonstrates any difference between x
and y
. This boils down to the following rules:
===
checks object identity: x === y
is true if x
and y
are the same object, stored at the same location in memory.x === y
is true if x
and y
have the same type – and thus the same structure – and their corresponding components are all recursively ===
.Int
or Float64
), x === y
is true if x
and y
contain exactly the same bits.These rules, applied recursively, define the behavior of ===
.
The ==
function, on the other hand, is user-definable, and implements "abstract value equality". Overloadability is one key difference:
===
is not overloadable – it is a builtin function with fixed, pre-defined behavior. You cannot extend or change its behavior.==
is overloadable – it is a normal (for Julia) generic function with infix syntax. It has fallback definitions that give it useful default behavior on user-defined types, but you can change that as you see fit by adding new, more specific methods to ==
for your types.To provide more detail about how ==
behaves for built-in types and how it should behave for user-defined types when people extend it, from the docs:
For example, all numeric types are compared by numeric value, ignoring type. Strings are compared as sequences of characters, ignoring encoding.
You can think of this as "intuitive equality". If two numbers are numerically equal, they are ==
:
julia> 1 == 1.0 == 1 + 0im == 1.0 + 0.0im == 1//1
true
julia> 0.5 == 1/2 == 1//2
true
Note, however that ==
implements exact numerical equality:
julia> 2/3 == 2//3
false
These values are unequal because 2/3
is the floating-point value 0.6666666666666666
, which is the closest Float64
to the mathematical value 2/3 (or in Julia notation for a rational values, 2//3
), but 0.6666666666666666
is not exactly equal to 2/3. Moreover, ==
Follows IEEE 754 semantics for floating-point numbers.
This includes some possibly unexpected properties:
0.0
and -0.0
): they are ==
, even though they behave differently and are thus not ===
.NaN
) values: they are not ==
to themselves, each other, or any other value; they are each ===
to themselves, but not !==
to each other since they have different bits.Examples:
julia> 0.0 === -0.0
false
julia> 0.0 == -0.0
true
julia> 1/0.0
Inf
julia> 1/-0.0
-Inf
julia> NaN === NaN
true
julia> NaN === -NaN
false
julia> -NaN === -NaN
true
julia> NaN == NaN
false
julia> NaN == -NaN
false
julia> NaN == 1.0
false
This is kind of confusing, but that's the IEEE standard.
Further, the docs for ==
also state:
Collections should generally implement
==
by calling==
recursively on all contents.
Thus, the notion of value equality as given by ==
is extended recursively to collections:
julia> [1, 2, 3] == [1, 2, 3]
true
julia> [1, 2, 3] == [1.0, 2.0, 3.0]
true
julia> [1, 2, 3] == Any[1//1, 2.0, 3 + 0im]
true
Accordingly, this inherits the foibles of scalar ==
comparisons:
julia> a = [1, NaN, 3]
3-element Array{Float64,1}:
1.0
NaN
3.0
julia> a == a
false
The ===
comparison, on the other hand always tests object identity, so even if two arrays have the same type and contain identical values, they are only equal if they are the same array:
julia> b = copy(a)
3-element Array{Float64,1}:
1.0
NaN
3.0
julia> a === a
true
julia> a === b
false
julia> b === b
true
The reason that a
and b
are not ===
is that even though they currently happen to contain the same data here, since they are mutable and not the same object, you could mutate one of them and then it would become apparent that they are different:
julia> a[1] = -1
-1
julia> a # different than before
3-element Array{Int64,1}:
-1
2
3
julia> b # still the same as before
3-element Array{Int64,1}:
1
2
3
Thus you can tell that a
and b
are different objects through mutation. The same logic doesn't apply to immutable objects: if they contain the same data, they are indistinguishable as long as they have the same value. Thus, immutable values are freed from the being tied to a specific location, which is one of the reasons that compilers are able to optimize uses of immutable values so effectively.
See Also:
===
means that it's actually the same object, i.e. the variables point to the same spot in memory. ==
means that the objects have the same values. For example:
julia> A = rand(5,5) #Make an array
5x5 Array{Float64,2}:
0.349193 0.408216 0.703084 0.163128 0.815687
0.211441 0.0185634 0.378299 0.0734293 0.187445
0.667637 0.139323 0.286794 0.359962 0.229784
0.476224 0.49812 0.648244 0.831006 0.1787
0.960756 0.488886 0.195973 0.148958 0.200619
julia> B = A # This sets the pointer of B to the pointer of A
5x5 Array{Float64,2}:
0.349193 0.408216 0.703084 0.163128 0.815687
0.211441 0.0185634 0.378299 0.0734293 0.187445
0.667637 0.139323 0.286794 0.359962 0.229784
0.476224 0.49812 0.648244 0.831006 0.1787
0.960756 0.488886 0.195973 0.148958 0.200619
julia> B === A # Same spot in memory
true
julia> B[1,1]=2 #Change a value of B
2
julia> B
5x5 Array{Float64,2}:
2.0 0.408216 0.703084 0.163128 0.815687
0.211441 0.0185634 0.378299 0.0734293 0.187445
0.667637 0.139323 0.286794 0.359962 0.229784
0.476224 0.49812 0.648244 0.831006 0.1787
0.960756 0.488886 0.195973 0.148958 0.200619
julia> A #Also changes A since they point to the same spot
5x5 Array{Float64,2}:
2.0 0.408216 0.703084 0.163128 0.815687
0.211441 0.0185634 0.378299 0.0734293 0.187445
0.667637 0.139323 0.286794 0.359962 0.229784
0.476224 0.49812 0.648244 0.831006 0.1787
0.960756 0.488886 0.195973 0.148958 0.200619
julia> B = copy(A) #Now make B a copy of A, no longer the same pointer
5x5 Array{Float64,2}:
2.0 0.408216 0.703084 0.163128 0.815687
0.211441 0.0185634 0.378299 0.0734293 0.187445
0.667637 0.139323 0.286794 0.359962 0.229784
0.476224 0.49812 0.648244 0.831006 0.1787
0.960756 0.488886 0.195973 0.148958 0.200619
julia> B === A # Now this is false
false
julia> B == A # This is still true
true
julia> B[1,1] = 1 #Changing B
1
julia> B
5x5 Array{Float64,2}:
1.0 0.408216 0.703084 0.163128 0.815687
0.211441 0.0185634 0.378299 0.0734293 0.187445
0.667637 0.139323 0.286794 0.359962 0.229784
0.476224 0.49812 0.648244 0.831006 0.1787
0.960756 0.488886 0.195973 0.148958 0.200619
julia> A #Now does not change A
5x5 Array{Float64,2}:
2.0 0.408216 0.703084 0.163128 0.815687
0.211441 0.0185634 0.378299 0.0734293 0.187445
0.667637 0.139323 0.286794 0.359962 0.229784
0.476224 0.49812 0.648244 0.831006 0.1787
0.960756 0.488886 0.195973 0.148958 0.200619
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With