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, MOD(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 . Por exemplo.: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
. É assim que
MOD(a, b)
funciona para os dividendos não negativos na coluna a
. 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,
MOD(a, b)
pode retornar um valor negativo quando a é negativo. Por exemplo.:MOD(-2, 5)
retorna -2
quando deve retornar 3
. MOD(-5, -3)
retorna -2
quando deve retornar 1
. Solução 2 (correta para todos os números):
SELECT a, b, CASE WHEN MOD(a, b) >= 0 THEN MOD(a, b) ELSE MOD(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 entre qualquer dois inteiros (negativos ou não negativos), você pode usar o
CASE WHEN
construção. Quando MOD(a, b)
é não negativo, o resto é simplesmente MOD(a, b)
. Caso contrário, temos que corrigir o resultado retornado por MOD(a, b)
. Como você obtém o resto correto quando
MOD()
retorna um valor negativo? Você deve adicionar o valor absoluto do divisor a MOD(a, b)
. Ou seja, faça MOD(a, b) + ABS(b)
:MOD(-2, 5)
retorna -2
quando deve retornar 3
. Você pode corrigir isso adicionando 5
. MOD(-5, -3)
retorna -2
quando deve retornar 1
. Você pode corrigir isso adicionando 3
. Quando
MOD(a, b)
retorna um número negativo, o CASE WHEN
o resultado deve ser MOD(a, b) + ABS(b)
. É assim que obtemos 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, você ainda não pode dividir nenhum número por
0
. Então, se b = 0
, você receberá um erro. Solução 3 (correta para todos os números):
SELECT a, b, MOD(a, b) + ABS(b) * (1 - SIGN(MOD(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:MOD(a, b) + ABS(b) * (1 - SIGN(MOD(a, b) + 0.5)) / 2
Na Solução 2,
MOD(a, b) + ABS(b)
foi retornado para casos em que MOD(a, b) < 0
. Observe que MOD(a, b) + ABS(b) = MOD(a, b) + ABS(b) * 1 when MOD(a, b) < 0
. Por outro lado, você retorna
MOD(a, b)
quando MOD(a, b) >= 0
. Observe que MOD(a, b) = MOD(a, b) + ABS(b) * 0 when MOD(a, b) >= 0
. Assim, podemos multiplicar
ABS(b)
por uma expressão que é igual a 1 para um MOD(a, b)
negativo e 0
para um MOD(a, b)
não negativo . Desde MOD(a, b)
é sempre um inteiro, a expressão MOD(a, b) + 0.5
é sempre positivo para MOD(a, b) ≥ 0
e negativo para MOD(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
. Aqui está como você corrige isso:(1 - 1) / 2 = 0
(1 - (-1)) / 2 = 1
Então, a expressão correta pela qual você multiplica
ABS(b)
é:(1 - SIGN(MOD(a, b) + 0.5)) / 2
Então, a fórmula inteira é:
MOD(a, b) + ABS(b) * (1 - SIGN(MOD(a, b) + 0.5)) / 2