Vấn đề:
Bạn muốn tìm phần còn lại (không âm).
Ví dụ:
Trong bảng numbers
, bạn có hai cột số nguyên:a
và 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 |
Bạn muốn tính phần còn lại từ việc chia a
bởi b
. Mỗi phần còn lại phải là một giá trị số nguyên không âm nhỏ hơn b
.
Giải pháp 1 (không hoàn toàn đúng):
SELECT a, b, a % b AS remainder FROM numbers;
Kết quả là:
a | b | phần còn lại |
---|---|---|
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 | lỗi |
0 | 0 | lỗi |
Thảo luận:
Giải pháp này hoạt động chính xác nếu a không âm. Tuy nhiên, khi nó âm, nó không tuân theo định nghĩa toán học về phần còn lại.
Về mặt khái niệm, phần còn lại là phần còn lại sau phép chia số nguyên của a
bởi b
. Về mặt toán học, phần dư của hai số nguyên là một số nguyên không âm nhỏ hơn ước số b
. Chính xác hơn, nó là một số r∈ {0,1, ..., b - 1} mà tồn tại một số nguyên k sao cho a =k * b + r.
Đây chính là cách a % b
hoạt động cho cổ tức không âm trong cột a
:
5 = 1 * 3 + 2
, vì vậy phần còn lại của 5 và 3 bằng 2
.
9 = 3 * 3 + 0
, vì vậy phần còn lại của 9 và 3 bằng 0
.
5 = (-1) * (-3) + 2
, vì vậy phần còn lại của 5 và -3 bằng 2
.
Rõ ràng, một lỗi được hiển thị nếu số chia b
là 0
, bởi vì bạn không thể chia cho 0
.
Lấy phần còn lại chính xác là vấn đề khi cổ tức a
là một số âm. Rất tiếc, a % b
có thể trả về giá trị âm khi a
là tiêu cực. Ví dụ:
-2 % 5
trả về -2
khi nào nó sẽ trả về 3
.
-5 % -3
trả về -2
khi nào nó sẽ trả về 1
.
Giải pháp 2 (đúng cho tất cả các số):
SELECT a, b, CASE WHEN a % b >= 0 THEN a % b ELSE a % b + ABS(b) END AS remainder FROM numbers;
Kết quả là:
a | b | phần còn lại |
---|---|---|
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 | lỗi |
0 | 0 | lỗi |
Thảo luận:
Để tính phần còn lại của sự phân chia bất kỳ hai số nguyên (âm hoặc không âm), bạn có thể sử dụng CASE WHEN
sự thi công. Nếu a % b
là không âm, phần còn lại chỉ đơn giản là a % b
. Nếu không, chúng tôi cần sửa kết quả trả về bởi a % b
.
Nếu a % b
trả về giá trị âm, bạn nên thêm giá trị tuyệt đối của một số chia vào a % b
. Đó là, hãy đặt nó thành a % b + ABS(b)
:
-2 % 5
trả về -2
khi nào nó sẽ trả về 3
. Bạn có thể sửa lỗi này bằng cách thêm 5
.
-5 % (-3)
trả về -2
khi nào nó sẽ trả về 1
. Bạn có thể sửa lỗi này bằng cách thêm 3
.
Khi a % b
trả về một giá trị âm, CASE WHEN
kết quả phải là a % b + ABS(b)
. Đây là cách bạn nhận được Giải pháp 2. Nếu bạn cần cập nhật về cách ABS()
chức năng hoạt động, hãy xem sách nấu ăn Cách tính giá trị tuyệt đối trong SQL.
Tất nhiên, nếu b = 0
, bạn sẽ vẫn gặp lỗi.
Giải pháp 3 (đúng cho tất cả các số):
SELECT a, b, a % b + ABS(b) * (1 - SIGN(a % b + 0.5)) / 2 AS remainder FROM numbers;
Kết quả là:
a | b | phần còn lại |
---|---|---|
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 | lỗi |
0 | 0 | lỗi |
Thảo luận:
Có một cách khác để giải quyết vấn đề này. Thay vì CASE WHEN
, sử dụng công thức toán học một dòng phức tạp hơn:
a % b + ABS(b) * (1 - SIGN(a % b + 0.5)) / 2
Trong Giải pháp 2, a % b + ABS(b)
được trả về cho các trường hợp khi a % b < 0
. Lưu ý rằng a % b + ABS(b) = a % b + ABS(b) * 1 when a % b < 0
.
Vì vậy, chúng ta có thể nhân ABS(b)
bởi một biểu thức bằng 1 cho các giá trị âm của a % b
và 0
cho các giá trị không âm của a % b
. Kể từ a % b
luôn là một số nguyên, biểu thức a % b + 0.5
luôn dương cho a % b >= 0
và phủ định cho a % b < 0
. Bạn có thể sử dụng bất kỳ số dương nào nhỏ hơn 1
thay vì 0.5
.
Hàm ký SIGN()
trả về 1
nếu đối số của nó là đúng, -1
nếu nó hoàn toàn phủ định và 0
nếu nó bằng 0
. Tuy nhiên, bạn cần thứ gì đó chỉ trả về 0
và 1
, không phải 1
và -1
. Nhưng đừng lo lắng! Đây là cách bạn khắc phục sự cố này:
(1 - 1) / 2 = 0
(1 - (-1)) / 2 = 1
Sau đó, biểu thức đúng mà bạn sẽ nhân với ABS(b)
là:
(1 - SIGN(a % b + 0.5)) / 2
Vì vậy, toàn bộ công thức là:
a % b + ABS(b) * (1 - SIGN(a % b + 0.5)) / 2