Проблем:
Искате да намерите (неотрицателния) остатък.
Пример:
В таблицата numbers
, имате две колони с цели числа: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 |
Искате да изчислите остатъците от разделянето на a
от b
. Всеки остатък трябва да бъде неотрицателна стойност, по-малка от b
.
Решение 1 (не е напълно правилно):
SELECT a, b, MOD(a, b) AS remainder FROM numbers;
Резултатът е:
a | b | остатък |
---|---|---|
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 | грешка |
0 | 0 | грешка |
Дискусия:
Това решение работи правилно, ако a е неотрицателно. Когато обаче е отрицателен, той не следва математическата дефиниция на остатъка.
Концептуално остатъкът е това, което остава след целочислено деление на a
от b
. Математически, остатъкът от две цели числа е цяло неотрицателно число, което е по-малко от делителя b
. По-точно, това е число r∈{0,1,...,b - 1}, за което съществува някакво цяло число k, такова че a =k * b + r . Напр.:
5 = 1 * 3 + 2
, така че остатъкът от 5 и 3 е равен на 2
.
9 = 3 * 3 + 0
, така че остатъкът от 9 и 3 е равен на 0
.
5 = (-1) * (-3) + 2
, така че остатъкът от 5 и -3 е равен на 2
.
Ето как MOD(a, b)
работи за неотрицателните дивиденти в колоната a
. Очевидно се показва грешка, ако делителят b
е 0
, защото не можете да разделите на 0
.
Получаването на правилния остатък е проблематично, когато дивидентът a е отрицателно число. За съжаление, MOD(a, b)
може да върне отрицателна стойност, когато a е отрицателно. Напр.:
MOD(-2, 5)
връща -2
когато трябва да върне 3
.
MOD(-5, -3)
връща -2
когато трябва да върне 1
.
Решение 2 (правилно за всички числа):
SELECT a, b, CASE WHEN MOD(a, b) >= 0 THEN MOD(a, b) ELSE MOD(a, b) + ABS(b) END AS remainder FROM numbers;
Резултатът е:
a | b | остатък |
---|---|---|
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 | грешка |
0 | 0 | грешка |
Дискусия:
За изчисляване на остатъка от деление между всякакво две цели числа (отрицателни или неотрицателни), можете да използвате CASE WHEN
строителство. Когато MOD(a, b)
е неотрицателен, остатъкът е просто MOD(a, b)
. В противен случай трябва да коригираме резултата, върнат от MOD(a, b)
.
Как да получите правилния остатък, когато MOD()
връща отрицателна стойност? Трябва да добавите абсолютната стойност на делителя към MOD(a, b)
. Тоест направете го MOD(a, b) + ABS(b)
:
MOD(-2, 5)
връща -2
когато трябва да върне 3
. Можете да коригирате това, като добавите 5
.
MOD(-5, -3)
връща -2
когато трябва да върне 1
. Можете да коригирате това, като добавите 3
.
Когато MOD(a, b)
връща отрицателно число, CASE WHEN
резултатът трябва да бъде MOD(a, b) + ABS(b)
. Ето как получаваме Решение 2. Ако имате нужда от опресняване на това как ABS()
функцията работи, погледнете готварската книга Как да изчислите абсолютна стойност в SQL.
Разбира се, все още не можете да разделите никое число на 0
. Така че, ако b = 0
, ще получите грешка.
Решение 3 (правилно за всички числа):
SELECT a, b, MOD(a, b) + ABS(b) * (1 - SIGN(MOD(a, b) + 0.5)) / 2 AS remainder FROM numbers;
Резултатът е:
a | b | остатък |
---|---|---|
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 | грешка |
0 | 0 | грешка |
Дискусия:
Има и друг начин за решаване на този проблем. Вместо CASE WHEN
, използвайте по-сложна едноредова математическа формула:
MOD(a, b) + ABS(b) * (1 - SIGN(MOD(a, b) + 0.5)) / 2
В решение 2, MOD(a, b) + ABS(b)
беше върнат за случаи, когато MOD(a, b) < 0
. Обърнете внимание, че MOD(a, b) + ABS(b) = MOD(a, b) + ABS(b) * 1 when MOD(a, b) < 0
.
За разлика от това, вие връщате MOD(a, b)
когато MOD(a, b) >= 0
. Обърнете внимание, че MOD(a, b) = MOD(a, b) + ABS(b) * 0 when MOD(a, b) >= 0
.
Така че можем да умножим ABS(b)
чрез израз, равен на 1 за отрицателен MOD(a, b)
и 0
за неотрицателен MOD(a, b)
. Тъй като MOD(a, b)
винаги е цяло число, изразът MOD(a, b) + 0.5
винаги е положителен за MOD(a, b) ≥ 0
и отрицателен за MOD(a, b) < 0
. Можете да използвате всяко положително число, по-малко от 1
вместо 0.5
.
Знаковата функция SIGN()
връща 1
ако аргументът му е строго положителен, -1
ако е строго отрицателен, и 0
ако е равно на 0
. Трябва ви обаче нещо, което връща само 0
и 1
, а не 1
и -1
. Ето как да коригирате това:
(1 - 1) / 2 = 0
(1 - (-1)) / 2 = 1
След това правилният израз, по който умножавате ABS(b)
е:
(1 - SIGN(MOD(a, b) + 0.5)) / 2
И така, цялата формула е:
MOD(a, b) + ABS(b) * (1 - SIGN(MOD(a, b) + 0.5)) / 2