Problema:
Vuoi trovare il resto (non negativo).
Esempio:
Nella tabella numbers
, hai due colonne di numeri interi: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 |
Vuoi calcolare i resti dalla divisione di a
da b
. Ogni resto deve essere un valore intero non negativo inferiore a b
.
Soluzione 1 (non del tutto corretta):
SELECT a, b, a % b AS remainder FROM numbers;
Il risultato è:
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 | errore |
0 | 0 | errore |
Discussione:
Questa soluzione funziona correttamente se a non è negativo. Tuttavia, quando è negativo, non segue la definizione matematica del resto.
Concettualmente, un resto è ciò che rimane dopo una divisione intera di a
da b
. Matematicamente, un resto di due numeri interi è un numero intero non negativo minore del divisore b
. Più precisamente, è un numero r∈{0,1,...,b - 1} per il quale esiste un numero intero k tale che a =k * b + r.
Questo è esattamente come a % b
funziona per i dividendi non negativi nella colonna a
:
5 = 1 * 3 + 2
, quindi il resto di 5 e 3 è uguale a 2
.
9 = 3 * 3 + 0
, quindi il resto di 9 e 3 è uguale a 0
.
5 = (-1) * (-3) + 2
, quindi il resto di 5 e -3 è uguale a 2
.
Ovviamente viene visualizzato un errore se il divisore b
è 0
, perché non puoi dividere per 0
.
Ottenere il resto corretto è problematico quando il dividendo a
è un numero negativo Sfortunatamente, a % b
può restituire un valore negativo quando a
è negativo. Es.:
-2 % 5
restituisce -2
quando dovrebbe restituire 3
.
-5 % -3
restituisce -2
quando dovrebbe restituire 1
.
Soluzione 2 (corretta per tutti i numeri):
SELECT a, b, CASE WHEN a % b >= 0 THEN a % b ELSE a % b + ABS(b) END AS remainder FROM numbers;
Il risultato è:
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 | errore |
0 | 0 | errore |
Discussione:
Per calcolare il resto di una divisione di qualsiasi due interi (negativi o non negativi), puoi usare il CASE WHEN
costruzione. Se a % b
non è negativo, il resto è semplicemente a % b
. Altrimenti, dobbiamo correggere il risultato restituito da a % b
.
Se a % b
restituisce un valore negativo, dovresti aggiungere il valore assoluto di un divisore a a % b
. Cioè, rendilo a % b + ABS(b)
:
-2 % 5
restituisce -2
quando dovrebbe restituire 3
. Puoi risolvere il problema aggiungendo 5
.
-5 % (-3)
restituisce -2
quando dovrebbe restituire 1
. Puoi risolvere il problema aggiungendo 3
.
Quando a % b
restituisce un valore negativo, il CASE WHEN
il risultato dovrebbe essere a % b + ABS(b)
. Ecco come ottenere la Soluzione 2. Se hai bisogno di un aggiornamento su come ABS()
funziona, dai un'occhiata al ricettario Come calcolare un valore assoluto in SQL.
Naturalmente, se b = 0
, continuerai a ricevere un errore.
Soluzione 3 (corretta per tutti i numeri):
SELECT a, b, a % b + ABS(b) * (1 - SIGN(a % b + 0.5)) / 2 AS remainder FROM numbers;
Il risultato è:
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 | errore |
0 | 0 | errore |
Discussione:
C'è un altro modo per risolvere questo problema. Invece di un CASE WHEN
, usa una formula matematica di una riga più complessa:
a % b + ABS(b) * (1 - SIGN(a % b + 0.5)) / 2
Nella Soluzione 2, a % b + ABS(b)
è stato restituito per i casi in cui a % b < 0
. Nota che a % b + ABS(b) = a % b + ABS(b) * 1 when a % b < 0
.
Quindi, possiamo moltiplicare ABS(b)
da un'espressione uguale a 1 per valori negativi di a % b
e 0
per valori non negativi di a % b
. Dal momento che a % b
è sempre un numero intero, l'espressione a % b + 0.5
è sempre positivo per a % b >= 0
e negativo per a % b < 0
. Puoi utilizzare qualsiasi numero positivo inferiore a 1
invece di 0.5
.
La funzione segno SIGN()
restituisce 1
se il suo argomento è strettamente positivo, -1
se è strettamente negativo e 0
se è uguale a 0
. Tuttavia, hai bisogno di qualcosa che restituisca solo 0
e 1
, non 1
e -1
. Ma non preoccuparti! Ecco come risolvere questo problema:
(1 - 1) / 2 = 0
(1 - (-1)) / 2 = 1
Quindi, l'espressione corretta per la quale devi moltiplicare ABS(b)
è:
(1 - SIGN(a % b + 0.5)) / 2
Quindi, l'intera formula è:
a % b + ABS(b) * (1 - SIGN(a % b + 0.5)) / 2