Problém:
Chcete najít (nezáporný) zbytek.
Příklad:
V tabulce numbers
, máte dva sloupce celých čísel:a
a b
.
a | b |
---|---|
9 | 3 |
5 | 3 |
2 | 3 |
0 | 3 |
-2 | 3 |
-5 | 3 |
-9 | 3 |
5 | -3 |
-5 | -3 |
5 | 0 |
0 | 0 |
Chcete vypočítat zbytek z dělení a
od b
. Každý zbytek by měl být nezáporné celé číslo menší než b
.
Řešení 1 (není zcela správné):
SELECT a, b, MOD(a, b) AS remainder FROM numbers;
Výsledek je:
a | b | zbytek |
---|---|---|
9 | 3 | 0 |
5 | 3 | 2 |
2 | 3 | 2 |
0 | 3 | 0 |
-2 | 3 | -2 |
-5 | 3 | -2 |
-9 | 3 | 0 |
5 | -3 | 2 |
-5 | -3 | -2 |
5 | 0 | chyba |
0 | 0 | chyba |
Diskuse:
Toto řešení funguje správně, pokud a není záporné. Když je však záporný, neřídí se matematickou definicí zbytku.
Koncepčně je zbytek to, co zůstane po celočíselném dělení a
od b
. Matematicky je zbytek dvou celých čísel nezáporné celé číslo, které je menší než dělitel b
. Přesněji je to číslo r∈{0,1,...,b - 1} pro které existuje nějaké celé číslo k takové, že a =k * b + r . Např.:
5 = 1 * 3 + 2
, takže zbytek 5 a 3 se rovná 2
.
9 = 3 * 3 + 0
, takže zbytek 9 a 3 se rovná 0
.
5 = (-1) * (-3) + 2
, takže zbytek 5 a -3 se rovná 2
.
Takto MOD(a, b)
funguje pro nezáporné dividendy ve sloupci a
. Je zřejmé, že pokud je dělitel b
, zobrazí se chyba je 0
, protože nemůžete dělit 0
.
Získání správného zbytku je problematické, když je dividenda a záporné číslo. Bohužel MOD(a, b)
může vrátit zápornou hodnotu, když je a záporné. Např.:
MOD(-2, 5)
vrátí -2
kdy by měl vrátit 3
.
MOD(-5, -3)
vrátí -2
kdy by měl vrátit 1
.
Řešení 2 (správné pro všechna čísla):
SELECT a, b, CASE WHEN MOD(a, b) >= 0 THEN MOD(a, b) ELSE MOD(a, b) + ABS(b) END AS remainder FROM numbers;
Výsledek je:
a | b | zbytek |
---|---|---|
9 | 3 | 0 |
5 | 3 | 2 |
2 | 3 | 2 |
0 | 3 | 0 |
-2 | 3 | 1 |
-5 | 3 | 1 |
-9 | 3 | 0 |
5 | -3 | 2 |
-5 | -3 | 1 |
5 | 0 | chyba |
0 | 0 | chyba |
Diskuse:
Chcete-li vypočítat zbytek dělení mezi jakékoli dvě celá čísla (záporná nebo nezáporná), můžete použít CASE WHEN
konstrukce. Když MOD(a, b)
je nezáporné, zbytek je jednoduše MOD(a, b)
. V opačném případě musíme opravit výsledek vrácený MOD(a, b)
.
Jak získáte správný zbytek, když MOD()
vrátí zápornou hodnotu? Absolutní hodnotu dělitele byste měli přidat do MOD(a, b)
. To znamená, že MOD(a, b) + ABS(b)
:
MOD(-2, 5)
vrátí -2
kdy by měl vrátit 3
. Můžete to opravit přidáním 5
.
MOD(-5, -3)
vrátí -2
kdy by měl vrátit 1
. Můžete to opravit přidáním 3
.
Když MOD(a, b)
vrátí záporné číslo, CASE WHEN
výsledek by měl být MOD(a, b) + ABS(b)
. Takto získáváme řešení 2. Pokud si potřebujete zopakovat, jak ABS()
funkce funguje, podívejte se do kuchařky Jak vypočítat absolutní hodnotu v SQL.
Samozřejmě stále nemůžete dělit žádné číslo 0
. Pokud tedy b = 0
, zobrazí se chyba.
Řešení 3 (správné pro všechna čísla):
SELECT a, b, MOD(a, b) + ABS(b) * (1 - SIGN(MOD(a, b) + 0.5)) / 2 AS remainder FROM numbers;
Výsledek je:
a | b | zbytek |
---|---|---|
9 | 3 | 0 |
5 | 3 | 2 |
2 | 3 | 2 |
0 | 3 | 0 |
-2 | 3 | 1 |
-5 | 3 | 1 |
-9 | 3 | 0 |
5 | -3 | 2 |
-5 | -3 | 1 |
5 | 0 | chyba |
0 | 0 | chyba |
Diskuse:
Existuje jiný způsob, jak tento problém vyřešit. Místo CASE WHEN
, použijte složitější jednořádkový matematický vzorec:
MOD(a, b) + ABS(b) * (1 - SIGN(MOD(a, b) + 0.5)) / 2
V řešení 2 MOD(a, b) + ABS(b)
byl vrácen pro případy, kdy MOD(a, b) < 0
. Všimněte si, že MOD(a, b) + ABS(b) = MOD(a, b) + ABS(b) * 1 when MOD(a, b) < 0
.
Naproti tomu vrátíte MOD(a, b)
když MOD(a, b) >= 0
. Všimněte si, že MOD(a, b) = MOD(a, b) + ABS(b) * 0 when MOD(a, b) >= 0
.
Můžeme tedy vynásobit ABS(b)
výrazem, který se rovná 1 pro záporné MOD(a, b)
a 0
pro nezáporný MOD(a, b)
. Od MOD(a, b)
je vždy celé číslo, výraz MOD(a, b) + 0.5
je vždy kladné pro MOD(a, b) ≥ 0
a záporné pro MOD(a, b) < 0
. Můžete použít jakékoli kladné číslo menší než 1
místo 0.5
.
Funkce znaku SIGN()
vrátí 1
pokud je jeho argument striktně kladný, -1
pokud je přísně záporné, a 0
pokud se rovná 0
. Potřebujete však něco, co vrátí pouze 0
a 1
, nikoli 1
a -1
. Zde je návod, jak to opravit:
(1 - 1) / 2 = 0
(1 - (-1)) / 2 = 1
Potom správný výraz, kterým vynásobíte ABS(b)
je:
(1 - SIGN(MOD(a, b) + 0.5)) / 2
Takže celý vzorec je:
MOD(a, b) + ABS(b) * (1 - SIGN(MOD(a, b) + 0.5)) / 2