Проблем:
Искате да намерите (неотрицателния) остатък.
Пример:
В таблицата 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