Problema:
Quiere encontrar el resto (no negativo).
Ejemplo:
En la tabla numbers
, tienes dos columnas de enteros:a
y 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 |
Quiere calcular los restos de dividir a
por b
. Cada residuo debe ser un valor entero no negativo menor que b
.
Solución 1 (no del todo correcta):
SELECT a, b, a % b AS remainder FROM numbers;
El resultado es:
a | b | resto |
---|---|---|
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 | error |
0 | 0 | error |
Discusión:
Esta solución funciona correctamente si a no es negativo. Sin embargo, cuando es negativo, no sigue la definición matemática del resto.
Conceptualmente, un resto es lo que queda después de una división entera de a
por b
. Matemáticamente, un resto de dos números enteros es un número entero no negativo que es más pequeño que el divisor b
. Más precisamente, es un número r∈{0,1,...,b - 1} para el cual existe algún entero k tal que a =k * b + r.
Así es exactamente como a % b
funciona para los dividendos no negativos en la columna a
:
5 = 1 * 3 + 2
, por lo que el resto de 5 y 3 es igual a 2
.
9 = 3 * 3 + 0
, por lo que el resto de 9 y 3 es igual a 0
.
5 = (-1) * (-3) + 2
, por lo que el resto de 5 y -3 es igual a 2
.
Obviamente, se muestra un error si el divisor b
es 0
, porque no puedes dividir por 0
.
Obtener el resto correcto es problemático cuando el dividendo a
es un número negativo. Desafortunadamente, a % b
puede devolver un valor negativo cuando a
es negativo Por ejemplo:
-2 % 5
devuelve -2
cuando debería devolver 3
.
-5 % -3
devuelve -2
cuando debería devolver 1
.
Solución 2 (correcta para todos los números):
SELECT a, b, CASE WHEN a % b >= 0 THEN a % b ELSE a % b + ABS(b) END AS remainder FROM numbers;
El resultado es:
a | b | resto |
---|---|---|
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 | error |
0 | 0 | error |
Discusión:
Para calcular el resto de una división de cualquier dos enteros (negativos o no negativos), puede usar el CASE WHEN
construcción. Si a % b
no es negativo, el resto es simplemente a % b
. De lo contrario, debemos corregir el resultado devuelto por a % b
.
Si a % b
devuelve un valor negativo, debe agregar el valor absoluto de un divisor a a % b
. Es decir, que sea a % b + ABS(b)
:
-2 % 5
devuelve -2
cuando debería devolver 3
. Puedes arreglar esto agregando 5
.
-5 % (-3)
devuelve -2
cuando debería devolver 1
. Puedes arreglar esto agregando 3
.
Cuando a % b
devuelve un valor negativo, el CASE WHEN
el resultado debería ser a % b + ABS(b)
. Así es como obtiene la Solución 2. Si necesita un repaso sobre cómo ABS()
funciona, eche un vistazo al libro de cocina Cómo calcular un valor absoluto en SQL.
Por supuesto, si b = 0
, seguirá recibiendo un error.
Solución 3 (correcta para todos los números):
SELECT a, b, a % b + ABS(b) * (1 - SIGN(a % b + 0.5)) / 2 AS remainder FROM numbers;
El resultado es:
a | b | resto |
---|---|---|
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 | error |
0 | 0 | error |
Discusión:
Hay otra manera de resolver este problema. En lugar de un CASE WHEN
, use una fórmula matemática de una línea más compleja:
a % b + ABS(b) * (1 - SIGN(a % b + 0.5)) / 2
En la Solución 2, a % b + ABS(b)
se devolvió para los casos en que a % b < 0
. Tenga en cuenta que a % b + ABS(b) = a % b + ABS(b) * 1 when a % b < 0
.
Entonces, podemos multiplicar ABS(b)
por una expresión que es igual a 1 para valores negativos de a % b
y 0
para valores no negativos de a % b
. Desde a % b
es siempre un número entero, la expresión a % b + 0.5
siempre es positivo para a % b >= 0
y negativo para a % b < 0
. Puede usar cualquier número positivo menor que 1
en lugar de 0.5
.
La función de signo SIGN()
devuelve 1
si su argumento es estrictamente positivo, -1
si es estrictamente negativo, y 0
si es igual a 0
. Sin embargo, necesita algo que devuelva solo 0
y 1
, no 1
y -1
. ¡Pero no te preocupes! Así es como solucionas esto:
(1 - 1) / 2 = 0
(1 - (-1)) / 2 = 1
Luego, la expresión correcta por la que debes multiplicar ABS(b)
es:
(1 - SIGN(a % b + 0.5)) / 2
Entonces, la fórmula completa es:
a % b + ABS(b) * (1 - SIGN(a % b + 0.5)) / 2