Problem:
Du vill hitta den (icke-negativa) resten.
Exempel:
I tabellen numbers
, du har två kolumner med heltal:a
och 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 |
Du vill beräkna resten genom att dividera a
av b
. Varje rest ska vara ett icke-negativt heltalsvärde som är mindre än b
.
Lösning 1 (inte helt korrekt):
SELECT a, b, MOD(a, b) AS remainder FROM numbers;
Resultatet är:
a | b | återstoden |
---|---|---|
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 | fel |
0 | 0 | fel |
Diskussion:
Denna lösning fungerar korrekt om a är icke-negativ. Men när den är negativ följer den inte den matematiska definitionen av resten.
Begreppsmässigt är en rest det som finns kvar efter en heltalsdelning av a
av b
. Matematiskt sett är en återstod av två heltal ett icke-negativt heltal som är mindre än divisorn b
. Mer exakt är det ett tal r∈{0,1,...,b - 1} för vilket det finns något heltal k så att a =k * b + r . T.ex.:
5 = 1 * 3 + 2
, så resten av 5 och 3 är lika med 2
.
9 = 3 * 3 + 0
, så resten av 9 och 3 är lika med 0
.
5 = (-1) * (-3) + 2
, så resten av 5 och -3 är lika med 2
.
Så här är MOD(a, b)
fungerar för de icke-negativa utdelningarna i kolumnen a
. Uppenbarligen visas ett fel om divisorn b
är 0
, eftersom du inte kan dividera med 0
.
Att få rätt återstod är problematiskt när utdelningen a är ett negativt tal. Tyvärr, MOD(a, b)
kan returnera ett negativt värde när a är negativt. T.ex.:
MOD(-2, 5)
returnerar -2
när den ska returnera 3
.
MOD(-5, -3)
returnerar -2
när den ska returnera 1
.
Lösning 2 (rätt för alla siffror):
SELECT a, b, CASE WHEN MOD(a, b) >= 0 THEN MOD(a, b) ELSE MOD(a, b) + ABS(b) END AS remainder FROM numbers;
Resultatet är:
a | b | återstoden |
---|---|---|
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 | fel |
0 | 0 | fel |
Diskussion:
För att beräkna resten av en division mellan alla två heltal (negativa eller icke-negativa), kan du använda CASE WHEN
konstruktion. När MOD(a, b)
är icke-negativ, resten är helt enkelt MOD(a, b)
. Annars måste vi korrigera resultatet som returneras av MOD(a, b)
.
Hur får du rätt återstod när MOD()
returnerar ett negativt värde? Du bör lägga till det absoluta värdet av divisorn till MOD(a, b)
. Det vill säga gör det till MOD(a, b) + ABS(b)
:
MOD(-2, 5)
returnerar -2
när den ska returnera 3
. Du kan fixa detta genom att lägga till 5
.
MOD(-5, -3)
returnerar -2
när den ska returnera 1
. Du kan fixa detta genom att lägga till 3
.
När MOD(a, b)
returnerar ett negativt tal, CASE WHEN
resultatet ska vara MOD(a, b) + ABS(b)
. Så här får vi lösning 2. Om du behöver en uppdatering om hur ABS()
funktionen fungerar, ta en titt i kokboken Hur man beräknar ett absolut värde i SQL.
Naturligtvis kan du fortfarande inte dividera något tal med 0
. Så, om b = 0
, får du ett felmeddelande.
Lösning 3 (korrekt för alla siffror):
SELECT a, b, MOD(a, b) + ABS(b) * (1 - SIGN(MOD(a, b) + 0.5)) / 2 AS remainder FROM numbers;
Resultatet är:
a | b | återstoden |
---|---|---|
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 | fel |
0 | 0 | fel |
Diskussion:
Det finns ett annat sätt att lösa detta problem. Istället för ett CASE WHEN
, använd en mer komplex enrads matematisk formel:
MOD(a, b) + ABS(b) * (1 - SIGN(MOD(a, b) + 0.5)) / 2
I lösning 2, MOD(a, b) + ABS(b)
returnerades för fall där MOD(a, b) < 0
. Observera att MOD(a, b) + ABS(b) = MOD(a, b) + ABS(b) * 1 when MOD(a, b) < 0
.
Däremot returnerar du MOD(a, b)
när MOD(a, b) >= 0
. Observera att MOD(a, b) = MOD(a, b) + ABS(b) * 0 when MOD(a, b) >= 0
.
Så vi kan multiplicera ABS(b)
med ett uttryck som är lika med 1 för en negativ MOD(a, b)
och 0
för en icke-negativ MOD(a, b)
. Sedan MOD(a, b)
är alltid ett heltal, uttrycket MOD(a, b) + 0.5
är alltid positiv för MOD(a, b) ≥ 0
och negativ för MOD(a, b) < 0
. Du kan använda vilket positivt tal som helst som är mindre än 1
istället för 0.5
.
Teckenfunktionen SIGN()
returnerar 1
om dess argument är strikt positivt, -1
om den är strikt negativ, och 0
om det är lika med 0
. Du behöver dock något som endast returnerar 0
och 1
, inte 1
och -1
. Så här fixar du detta:
(1 - 1) / 2 = 0
(1 - (-1)) / 2 = 1
Sedan, det korrekta uttrycket som du multiplicerar ABS(b)
med är:
(1 - SIGN(MOD(a, b) + 0.5)) / 2
Så hela formeln är:
MOD(a, b) + ABS(b) * (1 - SIGN(MOD(a, b) + 0.5)) / 2