Số tự nhiên và Số chấm động trong C++ (Integer, Floating point)

Dẫn nhập

Ở bài học trước, bạn đã nắm được BIẾN TRONG ₵++ (Variables), và đã biết nguyên lý hoạt động và một số kinh nghiệm về biến trong ₵++. Và bài học trước chỉ đề cập căn bản về biến của một số nguyên.

Trong ₵++ vẫn còn rất là nhiều kiểu dữ liệu khác, bạn sẽ được học 2 loại kiểu dữ liệu mới trong bài học lúc này: Số nguyên và Số chấm động trong ₵++ (Integer, Floating point)

Bài viết:

Để đọc hiểu bài này tốt nhất các chúng ta nên có học thức căn bản về phần:

Trong bài này, ta sẽ cùng khám phá các vấn đề:

  • Khái quát về kiểu dữ liệu căn bản trong ₵++
  • Kiểu số nguyên (Integer)
  • Kiểu số chấm động (Floating point)

Khái quát về kiểu dữ liệu căn bản trong ₵++

Ở bài học trước Biến trong ₵++ (Variables), bạn đã biết biến (variable) là tên của một vùng trong bộ nhớ lưu trữ Bộ nhớ đệm, được sử dụng để lưu trữ thông tin. Bạn có thể gán thông tin cho một biến, và có thể lấy thông tin đó ra để sử dụng. Có rất là nhiều loại thông tin (Chẳng hạn: thông tin dưới dạng số nguyên, số thực, ký tự, …), và trong ₵++, các biến cũng có thể lưu những loại thông tin khác nhau thông qua các kiểu dữ liệu khác nhau.

Kích cỡ của biến lệ thuộc vào kiểu dữ liệu của biến đó và quyết định số lượng thông tin mà biến đó lưu trữ. Khi bạn khai báo một biến, một vùng trong bộ nhớ lưu trữ sẽ giành cho biến đó. Ngày nay, việc khai báo biến với kích cỡ vài byte không là vấn đề gì, đối với độ lớn của bộ nhớ lưu trữ laptop. Nhưng nếu chương trình của các bạn có số lượng biến lên tới hàng triệu, thì việc phải sử dụng biến với kích cỡ sao cho thích hợp là điều rất trọng yếu.

Bảng bên dưới sẽ liệt kê những kiểu dữ liệu căn bản trong ₵++. Kích cỡ kiểu dữ liệu tương ứng bên dưới chỉ là kích cỡ nhỏ nhất có thể của kiểu dữ liệu đó. Trong thực tiễn, kích cỡ này lệ thuộc vào từng compiler và thiết kế laptop.

Để xác nhận kích cỡ của một kiểu dữ liệu trên một laptop rõ ràng, ₵++ phân phối cho bạn toán tử

sizeof

. Toán tử sizeof là toán tử một ngôi, nhận vào một kiểu dữ liệu hoặc một biến, và trả về kích cỡ (byte) của của kiểu dữ liệu hoặc biến đó.

Chẳng hạn:

#include <iostreamvàgt;
using namespace std;

int main()
{
	cout << "bool:tt" << sizeof(bool) << " bytes" << endl;
	cout << "char:tt" << sizeof(char) << " bytes" << endl;
	cout << "wchar_t:t" << sizeof(wchar_t) << " bytes" << endl; 

// C++11, may not be supported by your compiler
	cout << "char16_t:t" << sizeof(char16_t) << " bytes" << endl; 
	cout << "char32_t:t" << sizeof(char32_t) << " bytes" << endl; 

	cout << "short:tt" << sizeof(short) << " bytes" << endl;
	cout << "int:tt" << sizeof(int) << " bytes" << endl;
	cout << "long:tt" << sizeof(long) << " bytes" << endl; 

// C++11, may not be supported by your compiler
	cout << "long long:t" << sizeof(long long) << " bytes" << endl; 

	cout << "float:tt" << sizeof(float) << " bytes" << endl;
	cout << "double:tt" << sizeof(double) << " bytes" << endl;
	cout << "long double:t" << sizeof(long double) << " bytes" << endl;

	// You can also use the sizeof operator on α variable name
	int ռ;
	cout << "n variable:t" << sizeof(ռ) << " bytes" << endl;
	return 0;
}

Chương trình bên trên khi thực thi trên Window 7 x64 (Visual studio 2015) sẽ cho ra kết quả:

Xem Thêm  MongoDB tìm theo id - tìm theo id mongodb nút js

Một điều thú vị là toán tử sizeof là một trong 3 toán tử không phải là ký hiệu trong ₵++, 2 toán tử sót lại là new và delete sẽ được giới thiệu trong bài CẤP PHÁT ĐỘNG (Dynamic memory allocation).

Kiểu số nguyên (Integer)

Số nguyên là các số nguyên dương (1, 2, 3, …), các số đối (-1, -2, -3, …) và số 0. ₵++ có 5 loại số nguyên căn bản để sử dụng:

Cảnh báo: Char là một kiểu dữ liệu đặc biệt, nó vừa là kiểu số nguyên, cũng vừa là kiểu ký tự.

Cụ thể về thuộc tính ký tự của char sẽ được nói trong phần Character. Ở mục này, bạn tạm thời coi nó là một kiểu số nguyên bình bình.

Sự khác nhau giữa các kiểu số nguyên trên nằm ở kích thức. Kiểu có kích cỡ lớn sẽ lưu được những số nguyên lớn. Vùng giá trị của một kiểu số nguyên được xác nhận trên 2 yếu tố: kích cỡ và dấu của nó.

Số nguyên có dấu là những số nguyên dương (1, 2, 3, …), các số đối (-1, -2, -3, …) và số 0. Có 2 phương pháp để khai báo một biến số nguyên có dấu:

// Khai báo không tường minh, hay được dùng
char ͼ;
short s;
int ռ;
// Hoặc khai báo tường minh, sử dụng keyword signed
signed char ͼ;
signed short s;
signed int ռ;

Số nguyên không dấu là những số nguyên dương (1, 2, 3, …) và số 0. Thỉnh thoảng chương trình của các bạn có những biến không cần lưu trữ các số âm (Chẳng hạn: chiều cao, cân nặng, độ dài, chỉ số danh mục, …). Để khai báo số nguyên không dấu, bạn sử dụng keyword unsigned. Chẳng hạn:

// Sử dụng keyword unsigned
unsigned char uc;
unsigned short us;
unsigned int un;

Note: Một số nguyên không dấu chẳng thể lưu trữ các số âm, nhưng nó có thể lưu trữ số dương to hơn gấp 2 lần số nguyên có dấu.

Bên dưới là bảng miền giá trị số nguyên

Số chấm động (Floating point numbers)

 

Trong ₵++, kiểu số chấm động đại diện cho số thực (Chẳng hạn: 69.9696, 3.14159, 0.00001 …), dùng để lưu trữ những số rất lớn hoặc rất nhỏ. Cấu tạo lưu trữ bên trong của số thực được kiến trúc theo chuẩn số chấm động (floating-point) của IEEE.

Số chấm động không có keyword unsigned. Có 3 kiểu số chấm động khác nhau trong ₵++: float, double, long double.

Cảnh báo: 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 áp dụng.

Phương pháp để khái niệm một biến số chấm động:

// Definitions of floating point numbers
float fVarName;
double dVarName2;
long double ldVarName3;

Cảnh báo:

Khi bạn sử dụng một hằng số dưới dạng một số chấm động, quy ước số đó cần có tối thiểu 1 chữ số thập phân, điều này giúp phân biệt số chấm động và số nguyên.

Chẳng hạn:

// Initializations of floating point numbers
float fVarName{4.0f};  // 4.0 means floating point (ƒ 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 (ɭ suffix means long double)
int nVarName4{4};            // 4 means integer

Cảnh báo: Mặc định một hằ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 khắc phục những số rất lớn hoặc rất nhỏ. Chẳng hạn: chu kỳ xoay mặt trăng của Mộc Tinh là 152853.5047 s. Khi đó, bạn có thể viết bằng ký hiệu khoa học là 1.528535047 × 105 s. Hay một số khá thân thuộc với bạn như khối lượng của một electron là 9.1093822 Ҳ 10-31. Bên dưới là một số chẳng hạn khác:

Xem Thêm  Cách tạo kiểu cho các phần tử văn bản với phông chữ, kích thước và màu sắc trong CSS - cách thay đổi kiểu văn bản trong css

24327 = 2.4327 Ҳ 104

7354 = 7.354 Ҳ 103

0,0078 = 7.8 Ҳ 103

0,00069 = 6.9 Ҳ 104

Cảnh báo: Số mũ sẽ là dương nếu dấu thập phân chuyển sang phải, là âm nếu dấu thập phân chuyển sang trái.

Trong ₵++, bạn có thể sử dụng ký hiệu khoa học để gán giá trị cho biến số chấm động. Dùng ký hiệu ‘e’ hoặc ‘E’ để thay cho 10.

Chẳng hạn:

// Initializations of floating point numbers
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

Độ chuẩn xác của số chấm động (Precision)

Số chấm động sẽ bao gồm những số hữu hạn và vô hạn. So với số vô hạn, nghĩa là phần thập phân sẽ có bề dài vô hạn (Chẳng hạn: 1/6 = 0.1666666666666…, PI = 3.141592653589793…), nhưng bộ nhớ lưu trữ laptop và kích cỡ kiểu dữ liệu thì hữu hạn. Nên biến số chấm động chỉ lưu được một độ chuẩn xác khẳng định, và phần số sót lại phía sau sẽ bị mất.

Trong ₵++, khi xuất một số chấm động, std::cout mặc định số có 6 chữ số. Những số ngoài phạm vi sẽ bị cắt bỏ và làm tròn lên 1 nhà cung cấp nếu số bị cắt sau nó to hơn 5, hoặc số đó có thể được chuyển sang ký hiệu khoa học trong vài trường hợp tùy thuộc từng compiler. Chẳng hạn:

#include <iostreamvàgt;
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;
}

Chương trình bên trên khi thực thi trên Window 7 x64 (Visual studio 2015) sẽ cho ra kết quả:

Mặc dầu khi xuất một số chấm động, std::cout mặc định độ chuẩn xác có 6 chữ số, nhưng bạn vẫn có thể biến đổi được độ chuẩn xác này bằng cách dùng hàm std::setprecision() thuộc thư viện <iomanipvàgt;.

#include <iostreamvàgt;
#include <iomanipvàgt;	// for std::setprecision()
using namespace std;

int main()
{
	cout << std::setprecision(20);		// Show 20 digits

	float	ƒ{ 9.66666666666666666666f }; 	// Initializations
	cout << ƒ << endl;

double	{d}{ 9.66666666666666666666 };	// Initializations
	cout << {d} << endl;
	return 0;
}

Kết quả nhận được:

Trong chương trình trên, ta đã biến đổi độ chuẩn xác lên đến 20 chữ số thay vì là 6 chữ số như mặc định. Nhưng dù 2 biến floatdouble đều đã hiện đủ 20 chữ số, thì độ chuẩn xác của nó vẫn không đến 20 chữ số.

Thông thường số chấm động kiểu float có độ chuẩn xác đơn (single-precision), chuẩn xác đến 7 chữ số. Double có độ chuẩn xác kép (double-precision), chuẩn xác đến 16 chữ số. Này là nguyên nhân vì sao chương trình trên lại có những số rác sau khoảng chuẩn xác.

Độ chuẩn xác của số chấm động không những tác động trên phần thập phân, mà nó có thể tác động trên phần nguyên của những số có quá nhiều chữ số.

Chẳng hạn:

#include <iostreamvàgt;
#include <iomanipvàgt;	// for std::setprecision()
using namespace std;

int main()
{
	float	ƒ{ 123456789.0f };

	cout << std::setprecision(9);	// Show 9 digits
	cout << ƒ << endl;
	return 0;
}

Kết quả nhận được:

 

Vì kiểu float có độ chuẩn xác 7 chữ số, nên chương trình đã xuất ra 123.456.792, số này to hơn giá trị biến ban đầu rất là nhiều. Do vậy, chúng ta nên cẩn trọng khi sử dụng kiểu float để lưu trữ những số cần một độ chuẩn xác cao.

Cảnh báo: Chúng ta nên sử dụng kiểu double khi cần lưu trữ một số chấm động, giới hạn sử dụng float vì kiểu float có độ chuẩn xác thấp sẽ dẫn tới số không chuẩn xác.

Lỗi làm tròn số chấm động (Rounding errors)

Trong laptop, số chấm động được lưu dưới hệ nhị phân.

Xem Thêm  Difference Between strlen and sizeof in C [Code Explained in Detail] - strlen

Chẳng hạn: ta có phân số 1/10, = 0.1 trong hệ thập phân = 0.000110011(0011)… trong hệ nhị phân (lặp vô hạn). Ta thấy số 0.1 chuyển sang hệ nhị phân sẽ lặp vô hạn, nhưng độ chuẩn xác của số chấm động là hữu hạn. Dẫn theo việc nó chẳng thể được trình diễn một cách chuẩn xác như một giá trị nhị phân hữu hạn. Xét chẳng hạn:

#include <iostreamvàgt;
#include <iomanipvàgt;	// 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:

Trong chương trình trên, ta có một biến double {d}{0.1}. Khi output với độ chuẩn xác mặc định std::setprecision(6), ta thu được chuẩn xác 0.1. Nhưng khi output với std::setprecision(20), kết quả lại to hơn 0.1.

Kết quả cho thấy khi gán số 0.1 cho một biến số chấm động, biến đó sẽ không hoàn toàn bằng 0.1. Đó gọi là lỗi làm tròn số chấm động.

Xét tiếp chẳng hạn:

#include <iostreamvàgt;
#include <iomanipvàgt;	// for std::setprecision()
using namespace std;

int main()
{
	double	d1{ 1.0 };
	double	d2{ 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 };

	cout << std::setprecision(20);	// show 20 digits
	cout << d1 << endl;
	cout << d2 << endl;
	return 0;
}

Kết quả chương trình:

 

Trong chương trình trên, trong toán học thì 2 biến d1 = d2, nhưng trong lập trình biến d1 > d2 vì lỗi làm tròn số dấu chấm động.

Tương tự, bạn hãy thử với trường hợp 0.1 + 0.7 = 0.8 ?

Cảnh báo: Không lúc nào so sánh hai giá trị dấu chấm động bằng nhau hay không. Hầu hết luôn luôn có sự độc đáo nhỏ giữa hai số chấm động. Cách thông dụng để so sánh 2 số chấm động là tính khoảng cách giữa 2 số đó, nếu khoảng cách này là rất nhỏ thì ta nghĩ rằng 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 ₵++ (If statements).

Tổng kết

Qua bài học này, bạn đã nắm được kiểu Số nguyên và Số chấm động trong ₵++ (Integer, Floating point), và đã hiểu rằng những kinh nghiệm cũng như những lỗi thường gặp khi sử dụng nó.

Trong bài học kế tiếp, mình sẽ giới thiệu cho các bạn một kiểu dữ liệu khá trọng yếu trong lập trình: KIỂU KÝ TỰ TRONG ₵++ (Data types).

Cảm ơn các bạn đã theo dõi nội dung. Hãy để lại phản hồi hoặc phản hồi của mình để lớn mạnh nội dung tốt hơn. Đừng quên “Luyện tập – Thử thách – Không ngại khó”.

Luận bàn

Nếu bạn có bất kỳ khổ cực hay khúc mắc gì về khóa học, đừng ngần ngại đặt thắc mắc trong phần bên dưới hoặc trong mục HỎI & ĐÁP trên thư viện Howkteam.com để thu được sự trợ giúp từ cộng đồng.

Viết một bình luận