Julia Language
Porównania
Szukaj…
Składnia
- x <y # jeśli
x
jest ściśle mniejsze niży
- x> y # jeśli
x
jest ściśle większe niży
- x == y # jeśli
x
jest równey
- x === y # alternatywnie
x ≡ y
, jeślix
jest równey
- x ≤ y # alternatywnie
x <= y
, jeślix
jest mniejsze lub równey
- x ≥ y # alternatywnie
x >= y
, jeślix
jest większe lub równey
- x ≠ y # alternatywnie
x != y
, jeślix
nie jest równey
- x ≈ y # jeśli
x
jest w przybliżeniu równey
Uwagi
Uważaj na odwracanie znaków porównawczych. Julia domyślnie definiuje wiele funkcji porównawczych bez definiowania odpowiedniej odwróconej wersji. Na przykład można uruchomić
julia> Set(1:3) ⊆ Set(0:5)
true
ale to nie działa
julia> Set(0:5) ⊇ Set(1:3)
ERROR: UndefVarError: ⊇ not defined
Powiązane porównania
Wiele operatorów porównania używanych razem jest połączonych w łańcuch, tak jakby byli połączeni przez operatora &&
. Może to być przydatne w czytelnych i matematycznie zwięzłych łańcuchach porównawczych, takich jak
# same as 0 < i && i <= length(A)
isinbounds(A, i) = 0 < i ≤ length(A)
# same as Set() != x && issubset(x, y)
isnonemptysubset(x, y) = Set() ≠ x ⊆ y
Istnieje jednak istotna różnica między a > b > c
oraz a > b && b > c
; w tym ostatnim termin b
jest oceniany dwukrotnie. Nie ma to większego znaczenia dla zwykłych starych symboli, ale może mieć znaczenie, jeśli same terminy mają skutki uboczne. Na przykład,
julia> f(x) = (println(x); 2)
f (generic function with 1 method)
julia> 3 > f("test") > 1
test
true
julia> 3 > f("test") && f("test") > 1
test
test
true
Przyjrzyjmy się bliżej powiązanym połączeniom i ich działaniu, widząc, jak są one analizowane i przekształcane w wyrażenia . Po pierwsze, rozważmy proste porównanie, które widzimy, jest zwykłym starym wywołaniem funkcji:
julia> dump(:(a > b))
Expr
head: Symbol call
args: Array{Any}((3,))
1: Symbol >
2: Symbol a
3: Symbol b
typ: Any
Teraz, jeśli połączymy łańcuch porównania, zauważymy, że parsowanie uległo zmianie:
julia> dump(:(a > b >= c))
Expr
head: Symbol comparison
args: Array{Any}((5,))
1: Symbol a
2: Symbol >
3: Symbol b
4: Symbol >=
5: Symbol c
typ: Any
Po analizie wyrażenie jest następnie obniżane do ostatecznej postaci:
julia> expand(:(a > b >= c))
:(begin
unless a > b goto 3
return b >= c
3:
return false
end)
i zauważamy, że jest to to samo, co dla a > b && b >= c
:
julia> expand(:(a > b && b >= c))
:(begin
unless a > b goto 3
return b >= c
3:
return false
end)
Liczby porządkowe
Przyjrzymy się, jak zaimplementować niestandardowe porównania, implementując niestandardowy typ, liczby porządkowe . Aby uprościć implementację, skupimy się na niewielkim podzbiorze tych liczb: wszystkie liczby porządkowe do, ale bez ε not. Nasze wdrożenie koncentruje się na prostocie, a nie szybkości; jednak wdrożenie nie jest również powolne.
Liczby porządkowe przechowujemy według ich normalnej postaci Cantor . Ponieważ arytmetyka porządkowa nie jest przemienna, najpierw przyjmiemy wspólną konwencję przechowywania najbardziej znaczących terminów.
immutable OrdinalNumber <: Number
βs::Vector{OrdinalNumber}
cs::Vector{Int}
end
Ponieważ postać normalna Cantora jest unikalna, możemy testować równość poprzez rekurencyjną równość:
W wersji v0.5 jest bardzo miła składnia do wykonywania tego zwięźle:
import Base: ==
α::OrdinalNumber == β::OrdinalNumber = α.βs == β.βs && α.cs == β.cs
W przeciwnym razie zdefiniuj funkcję w sposób bardziej typowy:
import Base: ==
==(α::OrdinalNumber, β::OrdinalNumber) = α.βs == β.βs && α.cs == β.cs
Aby zakończyć nasze zamówienie, ponieważ ten typ ma całkowite zamówienie, powinniśmy przeciążyć funkcję isless
:
import Base: isless
function isless(α::OrdinalNumber, β::OrdinalNumber)
for i in 1:min(length(α.cs), length(β.cs))
if α.βs[i] < β.βs[i]
return true
elseif α.βs[i] == β.βs[i] && α.cs[i] < β.cs[i]
return true
end
end
return length(α.cs) < length(β.cs)
end
Aby przetestować nasze zamówienie, możemy stworzyć metody tworzenia liczb porządkowych. Zero, oczywiście, uzyskuje się przez brak terminów w normalnej formie Cantora:
const ORDINAL_ZERO = OrdinalNumber([], [])
Base.zero(::Type{OrdinalNumber}) = ORDINAL_ZERO
Możemy zdefiniować expω
do obliczenia ω^α
i użyć go do obliczenia 1 i ω:
expω(α) = OrdinalNumber([α], [1])
const ORDINAL_ONE = expω(ORDINAL_ZERO)
Base.one(::Type{OrdinalNumber}) = ORDINAL_ONE
const ω = expω(ORDINAL_ONE)
Mamy teraz w pełni funkcjonalną funkcję porządkowania liczb porządkowych:
julia> ORDINAL_ZERO < ORDINAL_ONE < ω < expω(ω)
true
julia> ORDINAL_ONE > ORDINAL_ZERO
true
julia> sort([ORDINAL_ONE, ω, expω(ω), ORDINAL_ZERO])
4-element Array{OrdinalNumber,1}:
OrdinalNumber(OrdinalNumber[],Int64[])
OrdinalNumber(OrdinalNumber[OrdinalNumber(OrdinalNumber[],Int64[])],[1])
OrdinalNumber(OrdinalNumber[OrdinalNumber(OrdinalNumber[OrdinalNumber(OrdinalNumber[],Int64[])],[1])],[1])
OrdinalNumber(OrdinalNumber[OrdinalNumber(OrdinalNumber[OrdinalNumber(OrdinalNumber[OrdinalNumber(OrdinalNumber[],Int64[])],[1])],[1])],[1])
W ostatnim przykładzie widzimy, że drukowanie liczb porządkowych mogłoby być lepsze, ale wynik jest zgodny z oczekiwaniami.
Standardowi operatorzy
Julia obsługuje bardzo duży zestaw operatorów porównania. Obejmują one
- Wszystkie następujące sekwencje Unicode:
> < >= ≥ <= ≤ == === ≡ != ≠ !== ≢ ∈ ∉ ∋ ∌ ⊆ ⊈ ⊂ ⊄ ⊊ ∝ ∊ ∍ ∥ ∦ ∷ ∺ ∻ ∽ ∾ ≁ ≃ ≄ ≅ ≆ ≇ ≈ ≉ ≊ ≋ ≌ ≍ ≎ ≐ ≑ ≒ ≓ ≔ ≕ ≖ ≗ ≘ ≙ ≚ ≛ ≜ ≝ ≞ ≟ ≣ ≦ ≧ ≨ ≩ ≪ ≫ ≬ ≭ ≮ ≯ ≰ ≱ ≲ ≳ ≴ ≵ ≶ ≷ ≸ ≹ ≺ ≻ ≼ ≽ ≾ ≿ ⊀ ⊁ ⊃ ⊅ ⊇ ⊉ ⊋ ⊏ ⊐ ⊑ ⊒ ⊜ ⊩ ⊬ ⊮ ⊰ ⊱ ⊲ ⊳ ⊴ ⊵ ⊶ ⊷ ⋍ ⋐ ⋑ ⋕ ⋖ ⋗ ⋘ ⋙ ⋚ ⋛ ⋜ ⋝ ⋞ ⋟ ⋠ ⋡ ⋢ ⋣ ⋤ ⋥ ⋦ ⋧ ⋨ ⋩ ⋪ ⋫ ⋬ ⋭ ⋲ ⋳ ⋴ ⋵ ⋶ ⋷ ⋸ ⋹ ⋺ ⋻ ⋼ ⋽ ⋾ ⋿ ⟈ ⟉ ⟒ ⦷ ⧀ ⧁ ⧡ ⧣ ⧤ ⧥ ⩦ ⩧ ⩪ ⩫ ⩬ ⩭ ⩮ ⩯ ⩰ ⩱ ⩲ ⩳ ⩴ ⩵ ⩶ ⩷ ⩸ ⩹ ⩺ ⩻ ⩼ ⩽ ⩾ ⩿ ⪀ ⪁ ⪂ ⪃ ⪄ ⪅ ⪆ ⪇ ⪈ ⪉ ⪊ ⪋ ⪌ ⪍ ⪎ ⪏ ⪐ ⪑ ⪒ ⪓ ⪔ ⪕ ⪖ ⪗ ⪘ ⪙ ⪚ ⪛ ⪜ ⪝ ⪞ ⪟ ⪠ ⪡ ⪢ ⪣ ⪤ ⪥ ⪦ ⪧ ⪨ ⪩ ⪪ ⪫ ⪬ ⪭ ⪮ ⪯ ⪰ ⪱ ⪲ ⪳ ⪴ ⪵ ⪶ ⪷ ⪸ ⪹ ⪺ ⪻ ⪼ ⪽ ⪾ ⪿ ⫀ ⫁ ⫂ ⫃ ⫄ ⫅ ⫆ ⫇ ⫈ ⫉ ⫊ ⫋ ⫌ ⫍ ⫎ ⫏ ⫐ ⫑ ⫒ ⫓ ⫔ ⫕ ⫖ ⫗ ⫘ ⫙ ⫷ ⫸ ⫹ ⫺ ⊢ ⊣
; - Wszystkie symbole w punkcie 1, poprzedzone kropką (
.
), Które mają być składane elementarnie; - Operatory
<:
,>:
,.!
iin
, które nie mogą być poprzedzone kropką (.
).
Nie wszystkie z nich mają definicję w standardowej bibliotece Base
. Są one jednak dostępne dla innych pakietów do zdefiniowania i używania w razie potrzeby.
W codziennym użyciu większość tych operatorów porównania nie ma znaczenia. Najczęściej używane są standardowe funkcje matematyczne do zamawiania; zobacz listę Składnia.
Podobnie jak większość innych operatorów w Julii, operatory porównania są funkcjami i można je wywoływać jako funkcje. Na przykład (<)(1, 2)
jest identyczne w znaczeniu 1 < 2
.
Używanie ==, === i isequal
Istnieją trzy operatory równości: ==
, ===
i isequal
. (Ostatni tak naprawdę nie jest operatorem, ale jest funkcją, a wszystkie operatory są funkcjami.)
Kiedy stosować ==
==
to równość wartości . Zwraca wartość true
gdy dwa obiekty reprezentują w ich obecnym stanie tę samą wartość.
Na przykład jest oczywiste, że
julia> 1 == 1
true
ale ponadto
julia> 1 == 1.0
true
julia> 1 == 1.0 + 0.0im
true
julia> 1 == 1//1
true
Prawa strona każdej z powyższych równości jest innego rodzaju , ale wciąż reprezentują tę samą wartość.
W przypadku obiektów zmiennych, takich jak tablice , ==
porównuje ich bieżącą wartość.
julia> A = [1, 2, 3]
3-element Array{Int64,1}:
1
2
3
julia> B = [1, 2, 3]
3-element Array{Int64,1}:
1
2
3
julia> C = [1, 3, 2]
3-element Array{Int64,1}:
1
3
2
julia> A == B
true
julia> A == C
false
julia> A[2], A[3] = A[3], A[2] # swap 2nd and 3rd elements of A
(3,2)
julia> A
3-element Array{Int64,1}:
1
3
2
julia> A == B
false
julia> A == C
true
Przez większość czasu ==
jest właściwym wyborem.
Kiedy stosować ===
===
jest operacją znacznie bardziej rygorystyczną niż ==
. Zamiast równości wartości mierzy egalność. Dwa obiekty są egal, jeśli sam program ich nie rozróżnia. Tak więc mamy
julia> 1 === 1
true
ponieważ nie ma sposobu na odróżnienie 1
od innej 1
. Ale
julia> 1 === 1.0
false
ponieważ chociaż 1
i 1.0
są tej samej wartości, są różnego rodzaju, więc program może je rozróżnić.
Ponadto,
julia> A = [1, 2, 3]
3-element Array{Int64,1}:
1
2
3
julia> B = [1, 2, 3]
3-element Array{Int64,1}:
1
2
3
julia> A === B
false
julia> A === A
true
co z początku może wydawać się zaskakujące! Jak program mógł rozróżnić dwa wektory A
i B
? Ponieważ wektory są zmienne, może modyfikować A
, a następnie zachowywać się inaczej niż B
Ale bez względu na to, jak modyfikuje A
, A
zawsze będzie zachowywać się tak samo jak samo A
Więc A
jest egal dla A
, ale nie jest egal dla B
Kontynuuj wzdłuż tej żyły, obserwuj
julia> C = A
3-element Array{Int64,1}:
1
2
3
julia> A === C
true
Przypisując A
do C
, mówimy, że C
dokonał aliasu A
Oznacza to, że stała się tylko inną nazwą dla A
Wszelkie modyfikacje dokonane w A
będą obserwowane również przez C
Dlatego nie ma sposobu na odróżnienie A
i C
, więc są one egal.
Kiedy stosować isequal
Różnica między ==
i isequal
jest bardzo subtelna. Największa różnica polega na tym, jak obsługiwane są liczby zmiennoprzecinkowe:
julia> NaN == NaN
false
Ten prawdopodobnie zaskakujący wynik jest zdefiniowany przez standard IEEE dla typów zmiennoprzecinkowych (IEEE-754). Ale nie jest to przydatne w niektórych przypadkach, takich jak sortowanie. isequal
jest przewidziany dla tych przypadków:
julia> isequal(NaN, NaN)
true
Po drugiej stronie widma ==
traktuje zero ujemne IEEE i zero dodatnie jako tę samą wartość (również określoną przez IEEE-754). Te wartości mają jednak różne reprezentacje w pamięci.
julia> 0.0
0.0
julia> -0.0
-0.0
julia> 0.0 == -0.0
true
Ponownie dla celów sortowania, isequal
rozróżnia je.
julia> isequal(0.0, -0.0)
false