Giới thiệu
Trong bài viết trước đó, bạn đã nắm được về BIẾN TRONG C++ và đã có kiến thức cơ bản về biến trong ngôn ngữ lập trình C++. Tuy nhiên, trong C++ vẫn còn nhiều kiểu dữ liệu khác mà bạn cần phải biết. Trong bài viết này, chúng ta sẽ học về hai kiểu dữ liệu mới: "Số nguyên" và "Số chấm động" trong C++.
Tổng quan về kiểu dữ liệu cơ bản trong C++
Trước khi bắt đầu, để hiểu bài viết này một cách tốt nhất, bạn nên có kiến thức cơ bản về biến trong C++.
Trong C++, kiểu dữ liệu xác định kích thước của biến và quy định số lượng thông tin mà biến đó có thể lưu trữ. Khi bạn khai báo một biến, một vùng nhớ trong RAM sẽ được dành cho biến đó. Hiện nay, việc khai báo biến với kích thước vài byte không còn là vấn đề quan trọng, so với kích thước RAM của máy tính hiện đại. Tuy nhiên, nếu chương trình của bạn sử dụng hàng triệu biến, việc sử dụng biến với kích thước phù hợp là rất quan trọng.
Dưới đây là bảng liệt kê các kiểu dữ liệu cơ bản trong C++. Kích thước kiểu dữ liệu chỉ là kích thước nhỏ nhất có thể của kiểu dữ liệu đó. Trong thực tế, kích thước này có thể phụ thuộc vào trình dịch và kiến trúc máy tính.
Kiểu nguyên (Integer)
Kiểu nguyên đại diện cho các số nguyên dương (1, 2, 3, ...) và số âm (-1, -2, -3, ...), cũng như số 0. Trong C++, có 5 loại số nguyên cơ bản để sử dụng:
- char: kích thước 1 byte, lưu trữ các ký tự ASCII.
- short: kích thước 2 byte, lưu trữ các số nguyên nhỏ.
- int: kích thước 4 byte, lưu trữ các số nguyên.
- long: kích thước 4 byte, lưu trữ các số nguyên lớn.
- long long: kích thước 8 byte, lưu trữ các số nguyên rất lớn.
Chú ý: Kiểu char là một kiểu dữ liệu đặc biệt, vừa là kiểu số nguyên, vừa là kiểu ký tự.
Sự khác nhau giữa các kiểu nguyên nằm ở kích thước. Kiểu có kích thước lớn sẽ lưu được những số nguyên lớn. Vùng giá trị của một kiểu nguyên được xác định bởi kích thước và dấu của nó.
Ví dụ:
#include
using namespace std;
int main() {
cout << "char: " << sizeof(char) << " byte" << endl;
cout << "short: " << sizeof(short) << " bytes" << endl;
cout << "int: " << sizeof(int) << " bytes" << endl;
cout << "long: " << sizeof(long) << " bytes" << endl;
cout << "long long: " << sizeof(long long) << " bytes" << endl;
return 0;
}
Khi chạy chương trình trên Windows 7 x64 (Visual Studio 2015), kết quả là:
char: 1 byte
short: 2 bytes
int: 4 bytes
long: 4 bytes
long long: 8 bytes
Số chấm động (Floating point numbers)
Trong C++, kiểu số chấm động đại diện cho các số thực (ví dụ: 69.9696, 3.14159, 0.00001...) và được sử dụng để lưu trữ những số rất lớn hoặc rất nhỏ. Cấu trúc lưu trữ bên trong của số thực được thiết kế theo chuẩn số chấm động của IEEE.
Số chấm động không có từ khóa unsigned. Có 3 kiểu số chấm động khác nhau trong C++: float, double, long double.
#include
using namespace std;
int main() {
cout << "float: " << sizeof(float) << " bytes" << endl;
cout << "double: " << sizeof(double) << " bytes" << endl;
cout << "long double: " << sizeof(long double) << " bytes" << endl;
return 0;
}
Khi chạy chương trình trên Windows 7 x64 (Visual Studio 2015), kết quả sẽ là:
float: 4 bytes
double: 8 bytes
long double: 12 bytes
Chú ý: Một số môi trường lập trình đồng nhất kiểu long double với kiểu double, nên kiểu này ít được sử dụng trong lập trình ứng dụng.
Trong C++, để định nghĩa một biến số chấm động, bạn có thể sử dụng các kiểu số chấm động float, double, long double.
Ví dụ:
#include
using namespace std;
int main() {
float fVarName{ 4.0f }; // 4.0 means floating point (f suffix means float)
double dVarName2{ 4.0 }; // 4.0 means floating point (double by default)
long double dVarName3{ 4.0L }; // 4.0 means floating point (L suffix means long double)
int nVarName4{ 4 }; // 4 means integer
return 0;
}
Chú ý: Mặc định một hằng số dạng số thực sẽ là kiểu double. Để có một số thực kiểu float, bạn cần thêm hậu tố 'f'.
Ký hiệu khoa học (Scientific notation)
Ký hiệu khoa học là cách biểu diễn các số rất lớn hoặc rất nhỏ. Ví dụ, chu kỳ xoay của Mặt Trăng là 152853.5047 giây. Khi đó, bạn có thể viết nó dưới dạng 1.528535047 × 10^5 giây. Một số ví dụ khác:
- 24327 = 2.4327 x 10^4
- 7354 = 7.354 x 10^3
- 0.0078 = 7.8 x 10^-3
- 0.00069 = 6.9 x 10^-4
Trong C++, bạn có thể sử dụng ký hiệu khoa học để gán giá trị cho biến số chấm động. Bạn có thể sử dụng ký hiệu 'e' hoặc 'E' để thay thế cho 10.
Ví dụ:
#include
using namespace std;
int main() {
double dVarName1{ 69000.0 };
double dVarName2{ 6.9e4 }; // 6.9e4 is equal to 69000.0
double dVarName3{ 0.00069 };
double dVarName4{ 6.9E-4 }; // 6.9e-4 is equal to 0.00069
return 0;
}
Độ chính xác của số chấm động (Precision)
Số chấm động có thể bao gồm cả số hữu hạn và số vô hạn. Đối với số vô hạn, nghĩa là phần thập phân của nó có độ dài vô hạn (ví dụ: 1/6 = 0.1666666666666..., PI = 3.141592653589793...), nhưng bộ nhớ máy tính và kích thước kiểu dữ liệu lại là hữu hạn. Do đó, biến số chấm động chỉ lưu trữ một độ chính xác nhất định, và phần phụ sau nó sẽ bị mất.
Trong C++, khi xuất một số chấm động, mặc định là số có 6 chữ số. Những số ngoài phạm vi này sẽ bị cắt bỏ và làm tròn lên nếu số sau cắt lớn hơn hoặc bằng 5, hoặc số đó có thể được biểu diễn trong dạng ký hiệu khoa học.
Ví dụ:
#include
using namespace std;
int main() {
double d;
d = 9.87654321;
cout << d << endl;
d = 987.654321;
cout << d << endl;
d = 987654.321;
cout << d << endl;
d = 9876543.21;
cout << d << endl;
d = 0.0000987654321;
cout << d << endl;
d = 1.23456789;
cout << d << endl;
return 0;
}
Khi chạy chương trình trên Windows 7 x64 (Visual Studio 2015), kết quả là:
9.87654
987.654
987654
9.87654e+006
0.0000987654
1.23457
Mặc dù mặc định là số chấm động có 6 chữ số, bạn có thể thay đổi độ chính xác bằng cách sử dụng hàm std::setprecision()
trong thư viện
.
Ví dụ:
#include
#include // for std::setprecision()
using namespace std;
int main() {
cout << std::setprecision(20); // Show 20 digits
float f{ 9.66666666666666666666f }; // Initializations
cout << f << endl;
double d{ 9.66666666666666666666 }; // Initializations
cout << d << endl;
return 0;
}
Kết quả thu được:
9.6666669845581054688
9.6666666666666660633
Trong chương trình trên, ta đã thay đổi độ chính xác lên đến 20 chữ số thay vì là 6 chữ số như mặc định. Tuy nhiên, mặc dù hai biến float và double hiển thị đủ 20 chữ số, độ chính xác thực sự của chúng vẫn không đến 20 chữ số.
Thông thường, số chấm động kiểu float có độ chính xác đơn (single-precision), chính xác đến 7 chữ số. Còn số chấm động kiểu double có độ chính xác kép (double-precision), chính xác đến 16 chữ số. Đó là lý do tại sao chương trình trên lại có những số thừa sau phần chính xác.
Độ chính xác của số chấm động không chỉ ảnh hưởng đến phần thập phân, mà nó cũng có thể ảnh hưởng đến phần nguyên của các số có quá nhiều chữ số.
Ví dụ:
#include
#include // for std::setprecision()
using namespace std;
int main() {
float f{ 123456789.0f };
cout << std::setprecision(9); // Show 9 digits
cout << f << endl;
return 0;
}
Kết quả thu được:
123456792
Vì kiểu float có độ chính xác 7 chữ số, nên chương trình đã xuất ra 123.456.792, số này lớn hơn giá trị ban đầu rất nhiều. Do đó, bạn phải cẩn thận khi sử dụng kiểu float để lưu trữ những số cần một độ chính xác cao.
Lỗi làm tròn số chấm động (Rounding errors)
Trong máy tính, số chấm động được lưu trữ dưới dạng hệ nhị phân.
Ví dụ, số 0.1 trong hệ thập phân được biểu diễn trong hệ nhị phân là 0.000110011(0011)... Số 0.1 khi chuyển sang hệ nhị phân sẽ là một chuỗi số lặp vô hạn, nhưng độ chính xác của số chấm động là hữu hạn. Do đó, nó không thể biểu diễn một cách chính xác như một giá trị nhị phân hữu hạn.
Ví dụ:
#include
#include // for std::setprecision()
using namespace std;
int main() {
double d{ 0.1 };
cout << d << endl; // use default cout precision of 6
cout << std::setprecision(20); // show 20 digits
cout << d << endl;
return 0;
}
Kết quả chương trình:
0.1
0.10000000000000000555
Trong chương trình trên, ta có một biến double d{0.1}. Khi xuất ra với độ chính xác mặc định (sử dụng toán tử << mà không sử dụng hàm std::setprecision()
), ta nhận được kết quả chính xác 0.1. Nhưng khi xuất ra với độ chính xác 20 chữ số, kết quả lại lớn hơn 0.1.
Tương tự, hãy thử với trường hợp 0.1 + 0.7 = 0.8?
Chú ý: Không bao giờ so sánh hai giá trị số chấm động bằng nhau. Hầu như luôn luôn có sự khác biệt rất nhỏ giữa hai số chấm động. Cách phổ biến để so sánh hai số chấm động là tính khoảng cách giữa hai số đó, nếu khoảng cách đó là rất nhỏ thì ta cho là bằng nhau. Giá trị dùng để so sánh với khoảng cách đó thường được gọi là epsilon
. Điều này sẽ được giải thích rõ hơn trong bài Câu điều kiện If trong C++ (If statements).
Kết luận
Qua bài viết này, bạn đã biết về kiểu Số nguyên và Số chấm động trong C++, cũng như những kinh nghiệm và lỗi thường gặp khi sử dụng chúng.
Trong bài viết tiếp theo, chúng ta sẽ giới thiệu một kiểu dữ liệu quan trọng khác trong lập trình: Kiểu ký tự trong C++.