Problema:
Você quer encontrar o resto (não negativo).
Exemplo:
Na tabela
numbers
, você tem duas colunas de inteiros:a
e 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 |
Você deseja calcular os restos da divisão de
a
por b
. Cada resto deve ser um valor inteiro não negativo menor que b
. Solução 1 (não totalmente correta):
SELECT a, b, a % b AS remainder FROM numbers;
O resultado é:
a | b | restante |
---|---|---|
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 | erro |
0 | 0 | erro |
Discussão:
Esta solução funciona corretamente se a for não negativo. No entanto, quando é negativo, não segue a definição matemática do resto.
Conceitualmente, um resto é o que resta após uma divisão inteira de
a
por b
. Matematicamente, um resto de dois inteiros é um inteiro não negativo que é menor que o divisor b
. Mais precisamente, é um número r∈{0,1,...,b - 1} para o qual existe algum inteiro k tal que a =k * b + r. É exatamente assim que
a % b
funciona para os dividendos não negativos na coluna a
:5 = 1 * 3 + 2
, então o restante de 5 e 3 é igual a 2
. 9 = 3 * 3 + 0
, então o restante de 9 e 3 é igual a 0
. 5 = (-1) * (-3) + 2
, então o restante de 5 e -3 é igual a 2
. Obviamente, um erro é mostrado se o divisor
b
é 0
, porque você não pode dividir por 0
. Obter o resto correto é problemático quando o dividendo
a
é um número negativo. Infelizmente, a % b
pode retornar um valor negativo quando a
é negativo. Por exemplo.:-2 % 5
retorna -2
quando deve retornar 3
. -5 % -3
retorna -2
quando deve retornar 1
. Solução 2 (correta para todos os números):
SELECT a, b, CASE WHEN a % b >= 0 THEN a % b ELSE a % b + ABS(b) END AS remainder FROM numbers;
O resultado é:
a | b | restante |
---|---|---|
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 | erro |
0 | 0 | erro |
Discussão:
Para calcular o restante de uma divisão de qualquer dois inteiros (negativos ou não negativos), você pode usar o
CASE WHEN
construção. Se a % b
é não negativo, o resto é simplesmente a % b
. Caso contrário, precisamos corrigir o resultado retornado por a % b
. Se
a % b
retorna um valor negativo, você deve adicionar o valor absoluto de um divisor a a % b
. Ou seja, faça a % b + ABS(b)
:-2 % 5
retorna -2
quando deve retornar 3
. Você pode corrigir isso adicionando 5
. -5 % (-3)
retorna -2
quando deve retornar 1
. Você pode corrigir isso adicionando 3
. Quando
a % b
retorna um valor negativo, o CASE WHEN
o resultado deve ser a % b + ABS(b)
. É assim que você obtém a Solução 2. Se você precisar de uma atualização sobre como o ABS()
funciona, dê uma olhada no livro de receitas Como calcular um valor absoluto em SQL. Claro, se
b = 0
, você ainda receberá um erro. Solução 3 (correta para todos os números):
SELECT a, b, a % b + ABS(b) * (1 - SIGN(a % b + 0.5)) / 2 AS remainder FROM numbers;
O resultado é:
a | b | restante |
---|---|---|
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 | erro |
0 | 0 | erro |
Discussão:
Existe outra maneira de resolver este problema. Em vez de um
CASE WHEN
, use uma fórmula matemática de uma linha mais complexa:a % b + ABS(b) * (1 - SIGN(a % b + 0.5)) / 2
Na Solução 2,
a % b + ABS(b)
foi retornado para casos em que a % b < 0
. Observe que a % b + ABS(b) = a % b + ABS(b) * 1 when a % b < 0
. Assim, podemos multiplicar
ABS(b)
por uma expressão que é igual a 1 para valores negativos de a % b
e 0
para valores não negativos de a % b
. Desde a % b
é sempre um inteiro, a expressão a % b + 0.5
é sempre positivo para a % b >= 0
e negativo para a % b < 0
. Você pode usar qualquer número positivo menor que 1
em vez de 0.5
. A função de sinal
SIGN()
retorna 1
se seu argumento for estritamente positivo, -1
se for estritamente negativo e 0
se for igual a 0
. No entanto, você precisa de algo que retorne apenas 0
e 1
, não 1
e -1
. Mas não se preocupe! Veja como você corrige isso:(1 - 1) / 2 = 0
(1 - (-1)) / 2 = 1
Então, a expressão correta pela qual você deve multiplicar
ABS(b)
é:(1 - SIGN(a % b + 0.5)) / 2
Então, a fórmula inteira é:
a % b + ABS(b) * (1 - SIGN(a % b + 0.5)) / 2