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, 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.
Dokładnie tak a % b
działa dla nieujemnych dywidend w kolumnie a
:
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
.
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 dywidenda a
jest liczbą ujemną. Niestety, a % b
może zwrócić wartość ujemną, gdy a
jest ujemny. Np.:
-2 % 5
zwraca -2
kiedy powinien zwrócić 3
.
-5 % -3
zwraca -2
kiedy powinien zwrócić 1
.
Rozwiązanie 2 (poprawne dla wszystkich liczb):
SELECT a, b, CASE WHEN a % b >= 0 THEN a % b ELSE 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ć resztę z dzielenia dowolnego dwie liczby całkowite (ujemna lub nieujemna), możesz użyć CASE WHEN
budowa. Jeśli a % b
jest nieujemna, reszta to po prostu a % b
. W przeciwnym razie musimy poprawić wynik zwrócony przez a % b
.
Jeśli a % b
zwraca wartość ujemną, należy dodać wartość bezwzględną dzielnika do a % b
. To znaczy, zrób to a % b + ABS(b)
:
-2 % 5
zwraca -2
kiedy powinien zwrócić 3
. Możesz to naprawić, dodając 5
.
-5 % (-3)
zwraca -2
kiedy powinien zwrócić 1
. Możesz to naprawić, dodając 3
.
Kiedy a % b
zwraca wartość ujemną, CASE WHEN
wynik powinien być a % b + ABS(b)
. W ten sposób otrzymujesz 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, jeśli b = 0
, nadal będzie się pojawiać błąd.
Rozwiązanie 3 (poprawne dla wszystkich liczb):
SELECT a, b, a % b + ABS(b) * (1 - SIGN(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:
a % b + ABS(b) * (1 - SIGN(a % b + 0.5)) / 2
W rozwiązaniu 2 a % b + ABS(b)
został zwrócony dla przypadków, gdy a % b < 0
. Zauważ, że a % b + ABS(b) = a % b + ABS(b) * 1 when a % b < 0
.
Możemy więc pomnożyć ABS(b)
przez wyrażenie równe 1 dla ujemnych wartości a % b
i 0
dla nieujemnych wartości a % b
. Od a % b
jest zawsze liczbą całkowitą, wyrażenie a % b + 0.5
jest zawsze dodatnia dla a % b >= 0
i ujemny dla 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
. Ale bez obaw! Oto jak to naprawić:
(1 - 1) / 2 = 0
(1 - (-1)) / 2 = 1
Następnie poprawne wyrażenie, przez które należy pomnożyć ABS(b)
jest:
(1 - SIGN(a % b + 0.5)) / 2
Tak więc cała formuła to:
a % b + ABS(b) * (1 - SIGN(a % b + 0.5)) / 2