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