在Julia中,“==”和“===”比较运算符有什么区别?

37

===== 在 Julia 中的比较运算符有什么区别?

2个回答

49
@ChrisRackauckas的回答在可变对象方面是准确的。然而,问题还有一些需要注意的地方,所以我会在这里详细阐述一下。
"==="运算符("is"函数的别名)实现了Henry Baker的EGAL谓词[1, 2]:当两个对象在程序上无法区分时,即你无法编写任何代码来展示x和y之间的差异时,"x === y"为真。这可以归结为以下规则:
  • 对于可变值(数组、可变复合类型),=== 检查对象标识:如果 xy 是相同的对象,存储在内存中的相同位置,则 x === y 为真。
  • 对于不可变复合类型,如果 xy 具有相同的类型 - 因此具有相同的结构 - 并且它们的对应组件都是递归的 ===,则 x === y 为真。
  • 对于位类型(像 IntFloat64 这样的不可变数据块),如果 xy 包含完全相同的位,则 x === y 为真。

这些规则递归应用,定义了 === 的行为。

另一方面,== 函数是用户可定义的,并实现 "抽象值相等性"。重载性是一个关键区别:

  • === 不能被重载 - 它是一个内置函数,具有固定的预定义行为。您不能扩展或更改它的行为。
  • == 可以被重载 - 它是一个普通(对于Julia来说)具有中缀语法的通用函数。它有回退定义,为用户定义的类型提供有用的默认行为,但您可以通过为您的类型添加新的、更具体的方法到 == 中来自定义其行为。

关于如何为内置类型和用户定义类型扩展 == 的行为,以及它在这些类型上的行为,可以参考 文档:

例如,所有数值类型都按数值比较,忽略类型。字符串则按字符序列比较,忽略编码。

您可以将其视为 "直观相等"。如果两个数字在数值上相等,则它们是 == 的:

julia> 1 == 1.0 == 1 + 0im == 1.0 + 0.0im == 1//1
true

julia> 0.5 == 1/2 == 1//2
true

请注意,==实现的是精确的数值相等性:
julia> 2/3 == 2//3
false

这些值不相等是因为 2/3 是浮点数值 0.6666666666666666,它是最接近数学值 2/3(或者在 Julia 中有理数表示法为 2//3)的 Float64,但 0.6666666666666666 不完全等于 2/3。此外,== 遵循浮点数 IEEE 754 语义。
这包括一些可能出乎意料的特性:
- 存在不同的正负浮点零(0.0-0.0):它们是 == 的,即使它们的行为不同,因此不是 ===。 - 存在许多不同的非数字 (NaN) 值:它们不等于自己、彼此或任何其他值;它们每个都是 === 自己,但不是 !== 彼此,因为它们具有不同的位。
示例:
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

这有点令人困惑,但这是IEEE标准。

此外,==的文档也指出:

集合通常应通过对所有内容递归调用 == 来实现 ==

因此, == 给出的值相等概念在集合中被递归地扩展:

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

因此,这继承了标量==比较的缺点:
julia> a = [1, NaN, 3]
3-element Array{Float64,1}:
   1.0
 NaN
   3.0

julia> a == a
false

另一方面,===比较始终测试对象标识,因此即使两个数组具有相同的类型并包含相同的值,只有它们是同一个数组时才相等:
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

ab不是===的原因是,尽管它们在此处当前包含相同的数据,但由于它们是可变的且不是同一对象,您可以更改其中一个,然后它将变得不同:

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

因此,你可以通过突变来判断ab是不同的对象。然而,相同的逻辑并不适用于不可变对象:如果它们包含相同的数据,则只要它们具有相同的值,它们就无法区分。因此,不可变值被释放出与特定位置绑定的束缚,这是编译器能够如此有效地优化使用不可变值的原因之一。
另请参见:
- 如何消除Julia中未更改字符串的“WARNING:redefining constant”警告?

28

=== 表示它们实际上是同一个对象,即变量指向内存中的同一位置。 == 表示对象具有相同的值。例如:

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

5
另外:1)== 可以被重载,而 === 不行。2)=== 比较不可变对象的内容。其思想是只有当无法区分 xy 时,x === y 才为真。这也被称为等值谓词。 - Toivo Henningsson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接