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