Toán tử số học
Sau bài này bạn sẽ làm được
- Giải thích và tránh bẫy chia số nguyên
- Xác định đúng dấu của kết quả phép lấy dư với số âm
- Phân biệt chia 0 với số nguyên và với số thực
- Dùng toán tử % để tách chữ số và quay vòng chỉ số
Năm toán tử số học của C trông rất quen thuộc, nhưng hai trong số đó có hành vi khác hẳn toán học phổ thông: phép chia cắt bỏ phần thập phân, và phép lấy dư cho ra số âm khi số bị chia âm.
#Năm toán tử
| Toán tử | Ví dụ với a=7, b=2 | Kết quả | Ghi chú |
|---|---|---|---|
| + | a + b | 9 | Cộng |
| - | a - b | 5 | Trừ |
| * | a * b | 14 | Nhân |
| / | a / b | 3 | Chia, cắt bỏ phần thập phân |
| % | a % b | 1 | Lấy dư, chỉ dùng cho số nguyên |
Ngoài ra còn hai toán tử một ngôi: dấu trừ đổi dấu và dấu cộng không làm gì. Dấu cộng một ngôi tồn tại chủ yếu cho đối xứng.
int x = 5;
int y = -x; /* -5 */
int z = +x; /* 5, không có tác dụng gì */#Bẫy chia số nguyên
Đây là bẫy số một của Chương 4. Khi cả hai toán hạng đều là số nguyên, phép chia cho ra số nguyên và phần thập phân bị vứt bỏ, không phải làm tròn.
printf("%d\n", 7 / 2); /* 3, không phải 3.5 và cũng không phải 4 */
printf("%d\n", 1 / 2); /* 0 */
printf("%d\n", 99 / 100); /* 0 */
printf("%d\n", -7 / 2); /* -3, cắt về phía số 0 */int a = 5, b = 2;
double tb = a / b;
printf("%f\n", tb); /* 2.000000
Phép chia xảy ra TRƯỚC khi gán,
lúc đó cả hai vẫn là int. */
double ty_le = 3 / 4 * 100;
printf("%f\n", ty_le); /* 0.000000, vì 3/4 bằng 0 */int a = 5, b = 2;
double tb = (double)a / b;
printf("%f\n", tb); /* 2.500000
Ép MỘT vế là đủ, vế kia tự nâng theo. */
double ty_le = 3.0 / 4 * 100;
printf("%f\n", ty_le); /* 75.000000 */Khi nào chia nguyên lại có ích
Đừng nghĩ chia nguyên chỉ là cái bẫy. Nó rất hữu ích khi bạn muốn đúng hành vi đó.
int tong_giay = 3725;
int gio = tong_giay / 3600; /* 1 */
int phut = (tong_giay % 3600) / 60; /* 2 */
int giay = tong_giay % 60; /* 5 */
printf("%02d:%02d:%02d\n", gio, phut, giay); /* 01:02:05 *//* Làm tròn lên khi chia số nguyên dương */
int so_trang = (so_muc + muc_moi_trang - 1) / muc_moi_trang;
/* Ví dụ: 25 mục, 10 mục mỗi trang
(25 + 10 - 1) / 10 = 34 / 10 = 3 trang. Đúng. */#Phép lấy dư
Toán tử % chỉ dùng được cho số nguyên. Với số thực, dùng fmod trong <math.h>.
printf("%d\n", 7 % 2); /* 1 */
printf("%d\n", 10 % 5); /* 0 */
/* printf("%f\n", 7.5 % 2); */ /* LỖI biên dịch */
#include <math.h>
printf("%f\n", fmod(7.5, 2)); /* 1.500000 */if (n % 2 == 1) {
printf("lẻ");
}
/* Sai với n âm: -3 % 2 bằng -1, không bằng 1 */if (n % 2 != 0) {
printf("lẻ");
}
/* Đúng với mọi n */
/* Hoặc dùng toán tử bit, nhanh hơn và luôn đúng với n không âm */
if (n & 1) {
printf("lẻ");
}#Chia cho 0
Đây là chỗ số nguyên và số thực có hành vi hoàn toàn khác nhau.
| Biểu thức | Kết quả | Ghi chú |
|---|---|---|
| 5 / 0 | Hành vi không xác định | Thường làm chương trình dừng ngay lập tức |
| 5 % 0 | Hành vi không xác định | Tương tự |
| 5.0 / 0.0 | Dương vô cùng | Theo chuẩn IEEE 754, không dừng |
| -5.0 / 0.0 | Âm vô cùng | Theo chuẩn IEEE 754 |
| 0.0 / 0.0 | Không phải một số | Giá trị NaN |
#include <stdio.h>
#include <math.h>
int main(void)
{
double a = 5.0 / 0.0;
double b = 0.0 / 0.0;
printf("%f\n", a); /* inf */
printf("%f\n", b); /* nan hoặc -nan */
printf("%d\n", isinf(a)); /* 1 */
printf("%d\n", isnan(b)); /* 1 */
printf("%d\n", b == b); /* 0, NaN không bằng chính nó */
return 0;
}#Ứng dụng của phép lấy dư
Tách chữ số
int n = 1234;
while (n > 0) {
int chu_so = n % 10; /* lấy chữ số cuối */
printf("%d ", chu_so); /* 4 3 2 1 */
n /= 10; /* bỏ chữ số cuối */
}Quay vòng chỉ số
/* Bộ đệm vòng: khi tới cuối mảng thì quay lại đầu */
int buf[8];
int vi_tri = 0;
void them(int x)
{
buf[vi_tri] = x;
vi_tri = (vi_tri + 1) % 8; /* 0,1,2,...,7,0,1,... */
}Kiểm tra chia hết
if (n % 3 == 0 && n % 5 == 0) {
printf("FizzBuzz");
}
/* Năm nhuận */
int nhuan = (nam % 4 == 0 && nam % 100 != 0) || (nam % 400 == 0);Đổi hệ cơ số
/* Đổi số nguyên sang hệ nhị phân */
void in_nhi_phan(unsigned n)
{
char buf[33];
int i = 32;
buf[i] = '\0';
do {
buf[--i] = '0' + (n % 2);
n /= 2;
} while (n > 0);
printf("%s\n", buf + i);
}Tự làm thử
- Nhập số giây, đổi ra dạng
giờ:phút:giâyvới hai chữ số mỗi phần. - Nhập số có ba chữ số, in tổng và tích các chữ số của nó.
- Đảo ngược một số nguyên, ví dụ 1234 thành 4321.
- Nhập số tiền, in số tờ của mỗi mệnh giá theo thứ tự giảm dần: 500, 200, 100, 50, 20, 10, 5, 2, 1 nghìn.
- Viết hàm kiểm tra số lẻ chạy đúng với cả số âm, rồi kiểm thử với các giá trị âm.
- Viết hàm
double chia_an_toan(double a, double b, int *loi)trả về 0 và đặt cờ lỗi khi mẫu số bằng 0. - Viết chương trình đổi số nguyên sang hệ 2, 8 và 16, chỉ dùng phép chia và lấy dư.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- Chia hai số nguyên cho ra số nguyên, phần thập phân bị cắt bỏ chứ không làm tròn.
- Ép kiểu phải xảy ra trước phép chia:
(double)a / bđúng,(double)(a / b)sai. - Dấu của
%theo số bị chia, nên-3 % 2bằng −1. Kiểm tra số lẻ phải viếtn % 2 != 0. %chỉ dùng cho số nguyên, số thực thì dùngfmod.- Chia số nguyên cho 0 là hành vi không xác định, chia số thực cho 0 cho ra vô cùng hoặc NaN.
- Phép lấy dư dùng để tách chữ số, quay vòng chỉ số, kiểm tra chia hết và đổi hệ cơ số.