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, MOD(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 que existe algún entero k tal que a =k * b + r . Por ejemplo:
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
.
Así es como MOD(a, b)
funciona para los dividendos no negativos en la columna a
. 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, MOD(a, b)
puede devolver un valor negativo cuando a es negativo. Por ejemplo:
MOD(-2, 5)
devuelve -2
cuando debería devolver 3
.
MOD(-5, -3)
devuelve -2
cuando debería devolver 1
.
Solución 2 (correcta para todos los 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;
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 entre cualquiera dos enteros (negativos o no negativos), puede usar el CASE WHEN
construcción. Cuando MOD(a, b)
no es negativo, el resto es simplemente MOD(a, b)
. De lo contrario, debemos corregir el resultado devuelto por MOD(a, b)
.
¿Cómo se obtiene el resto correcto cuando MOD()
devuelve un valor negativo? Debe agregar el valor absoluto del divisor a MOD(a, b)
. Es decir, hazlo MOD(a, b) + ABS(b)
:
MOD(-2, 5)
devuelve -2
cuando debería devolver 3
. Puedes arreglar esto agregando 5
.
MOD(-5, -3)
devuelve -2
cuando debería devolver 1
. Puedes arreglar esto agregando 3
.
Cuando MOD(a, b)
devuelve un número negativo, el CASE WHEN
el resultado debería ser MOD(a, b) + ABS(b)
. Así es como obtenemos 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, todavía no puedes dividir ningún número por 0
. Entonces, si b = 0
, obtendrá un error.
Solución 3 (correcta para todos los números):
SELECT a, b, MOD(a, b) + ABS(b) * (1 - SIGN(MOD(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:
MOD(a, b) + ABS(b) * (1 - SIGN(MOD(a, b) + 0.5)) / 2
En la Solución 2, MOD(a, b) + ABS(b)
se devolvió para los casos en que MOD(a, b) < 0
. Tenga en cuenta que MOD(a, b) + ABS(b) = MOD(a, b) + ABS(b) * 1 when MOD(a, b) < 0
.
Por el contrario, devuelve MOD(a, b)
cuando MOD(a, b) >= 0
. Tenga en cuenta que MOD(a, b) = MOD(a, b) + ABS(b) * 0 when MOD(a, b) >= 0
.
Entonces, podemos multiplicar ABS(b)
por una expresión que es igual a 1 para un MOD(a, b)
negativo y 0
para un MOD(a, b)
no negativo . Desde MOD(a, b)
es siempre un número entero, la expresión MOD(a, b) + 0.5
siempre es positivo para MOD(a, b) ≥ 0
y negativo para MOD(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
. Así es como solucionas esto:
(1 - 1) / 2 = 0
(1 - (-1)) / 2 = 1
Luego, la expresión correcta por la que multiplicas ABS(b)
es:
(1 - SIGN(MOD(a, b) + 0.5)) / 2
Entonces, la fórmula completa es:
MOD(a, b) + ABS(b) * (1 - SIGN(MOD(a, b) + 0.5)) / 2