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