Julia Language
Porównania
Szukaj…
Składnia
- x <y # jeśli
xjest ściśle mniejsze niży - x> y # jeśli
xjest ściśle większe niży - x == y # jeśli
xjest równey - x === y # alternatywnie
x ≡ y, jeślixjest równey - x ≤ y # alternatywnie
x <= y, jeślixjest mniejsze lub równey - x ≥ y # alternatywnie
x >= y, jeślixjest większe lub równey - x ≠ y # alternatywnie
x != y, jeślixnie jest równey - x ≈ y # jeśli
xjest 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