Problem:
Chcesz znaleźć (nieujemną) resztę.
Przykład:
W tabeli numbers
, masz dwie kolumny liczb całkowitych:a
i 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 |
Chcesz obliczyć reszty z dzielenia a
przez b
. Każda reszta powinna być nieujemną liczbą całkowitą mniejszą niż b
.
Rozwiązanie 1 (nie do końca poprawne):
SELECT a, b, MOD(a, b) AS remainder FROM numbers;
Wynik:
a | b | pozostałe |
---|---|---|
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 | błąd |
0 | 0 | błąd |
Dyskusja:
To rozwiązanie działa poprawnie, jeśli a jest nieujemne. Jednak gdy jest ujemny, nie jest zgodny z matematyczną definicją reszty.
Koncepcyjnie reszta jest tym, co pozostaje po dzieleniu całkowitym a
przez b
. Matematycznie reszta dwóch liczb całkowitych jest nieujemną liczbą całkowitą, która jest mniejsza niż dzielnik b
. Dokładniej jest to liczba r∈{0,1,...,b - 1} dla której istnieje pewna liczba całkowita k taka, że a =k * b + r . Np.:
5 = 1 * 3 + 2
, więc reszta z 5 i 3 równa się 2
.
9 = 3 * 3 + 0
, więc reszta z 9 i 3 równa się 0
.
5 = (-1) * (-3) + 2
, więc reszta z 5 i -3 równa się 2
.
W ten sposób MOD(a, b)
działa dla nieujemnych dywidend w kolumnie a
. Oczywiście błąd jest wyświetlany, jeśli dzielnik b
to 0
, ponieważ nie można dzielić przez 0
.
Uzyskanie prawidłowej reszty jest problematyczne, gdy dzielna a jest liczbą ujemną. Niestety, MOD(a, b)
może zwrócić wartość ujemną, gdy a jest ujemne. Np.:
MOD(-2, 5)
zwraca -2
kiedy powinien zwrócić 3
.
MOD(-5, -3)
zwraca -2
kiedy powinien zwrócić 1
.
Rozwiązanie 2 (poprawne dla wszystkich liczb):
SELECT a, b, CASE WHEN MOD(a, b) >= 0 THEN MOD(a, b) ELSE MOD(a, b) + ABS(b) END AS remainder FROM numbers;
Wynik:
a | b | pozostałe |
---|---|---|
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 | błąd |
0 | 0 | błąd |
Dyskusja:
Aby obliczyć pozostałą część dzielenia między dowolnym dwie liczby całkowite (ujemna lub nieujemna), możesz użyć CASE WHEN
budowa. Kiedy MOD(a, b)
jest nieujemna, reszta to po prostu MOD(a, b)
. W przeciwnym razie musimy poprawić wynik zwrócony przez MOD(a, b)
.
Jak uzyskać poprawną resztę, gdy MOD()
zwraca wartość ujemną? Powinieneś dodać wartość bezwzględną dzielnika do MOD(a, b)
. Oznacza to, że zrób to MOD(a, b) + ABS(b)
:
MOD(-2, 5)
zwraca -2
kiedy powinien zwrócić 3
. Możesz to naprawić, dodając 5
.
MOD(-5, -3)
zwraca -2
kiedy powinien zwrócić 1
. Możesz to naprawić, dodając 3
.
Kiedy MOD(a, b)
zwraca liczbę ujemną, CASE WHEN
wynik powinien mieć postać MOD(a, b) + ABS(b)
. W ten sposób otrzymujemy Rozwiązanie 2. Jeśli potrzebujesz odświeżenia na temat sposobu ABS()
funkcja działa, spójrz na książkę kucharską Jak obliczyć wartość bezwzględną w SQL.
Oczywiście nadal nie możesz dzielić żadnej liczby przez 0
. Tak więc, jeśli b = 0
, pojawi się błąd.
Rozwiązanie 3 (poprawne dla wszystkich liczb):
SELECT a, b, MOD(a, b) + ABS(b) * (1 - SIGN(MOD(a, b) + 0.5)) / 2 AS remainder FROM numbers;
Wynik:
a | b | pozostałe |
---|---|---|
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 | błąd |
0 | 0 | błąd |
Dyskusja:
Jest inny sposób rozwiązania tego problemu. Zamiast CASE WHEN
, użyj bardziej złożonego jednowierszowego wzoru matematycznego:
MOD(a, b) + ABS(b) * (1 - SIGN(MOD(a, b) + 0.5)) / 2
W rozwiązaniu 2 MOD(a, b) + ABS(b)
został zwrócony dla przypadków, gdy MOD(a, b) < 0
. Zauważ, że MOD(a, b) + ABS(b) = MOD(a, b) + ABS(b) * 1 when MOD(a, b) < 0
.
Natomiast zwracasz MOD(a, b)
gdy MOD(a, b) >= 0
. Zauważ, że MOD(a, b) = MOD(a, b) + ABS(b) * 0 when MOD(a, b) >= 0
.
Możemy więc pomnożyć ABS(b)
przez wyrażenie równe 1 dla ujemnego MOD(a, b)
i 0
dla nieujemnego MOD(a, b)
. Od MOD(a, b)
jest zawsze liczbą całkowitą, wyrażenie MOD(a, b) + 0.5
jest zawsze dodatnia dla MOD(a, b) ≥ 0
i negatywne dla MOD(a, b) < 0
. Możesz użyć dowolnej liczby dodatniej mniejszej niż 1
zamiast 0.5
.
Funkcja znaku SIGN()
zwraca 1
jeśli jego argument jest ściśle dodatni, -1
jeśli jest ściśle ujemna, a 0
jeśli równa się 0
. Jednak potrzebujesz czegoś, co zwraca tylko 0
i 1
, a nie 1
i -1
. Oto jak to naprawić:
(1 - 1) / 2 = 0
(1 - (-1)) / 2 = 1
Następnie poprawne wyrażenie, przez które mnożysz ABS(b)
jest:
(1 - SIGN(MOD(a, b) + 0.5)) / 2
Tak więc cała formuła to:
MOD(a, b) + ABS(b) * (1 - SIGN(MOD(a, b) + 0.5)) / 2