Toán tử quan hệ là toán tử cho phép bạn so sánh hai giá trị. Có 6 toán tử quan hệ:

Toán tửKý hiệuFormHoạt động
Lớn>x > ytrue nếu x lớn hơn y, false nếu không
<x < ytrue nếu x nhỏ hơn y, false nếu không
Lớn hơn hoặc bằng>=x >= y true nếu x lớn hơn hoặc bằng y, false nếu không
Bé hơn hoặc bằng<=x <= ytrue nếu x nhỏ hơn hoặc bằng y, false nếu ngược lại
Bằng==x == ytrue nếu x bằng y, false nếu không
Khá nhau!=x != ytrue nếu x không bằng y, false nếu ngược lại

Bạn đã thấy hầu hết những thứ này hoạt động như thế nào và chúng khá trực quan. Mỗi toán tử này đánh giá giá trị boolean true (1) hoặc false (0).

Dưới đây là một số code mẫu sử dụng các toán tử này với số nguyên:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Group: https://www.facebook.com/groups/cafedev.vn/
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
Pinterest: https://www.pinterest.com/cafedevvn/
YouTube: https://www.youtube.com/channel/UCE7zpY_SlHGEgo67pHxqIoA/
*/

#include <iostream>
 
int main()
{
    std::cout << "Enter an integer: ";
    int x{};
    std::cin >> x;
 
    std::cout << "Enter another integer: ";
    int y{};
    std::cin >> y;
 
    if (x == y)
        std::cout << x << " equals " << y << '\n';
    if (x != y)
        std::cout << x << " does not equal " << y << '\n';
    if (x > y)
        std::cout << x << " is greater than " << y << '\n';
    if (x < y)
        std::cout << x << " is less than " << y << '\n';
    if (x >= y)
        std::cout << x << " is greater than or equal to " << y << '\n';
    if (x <= y)
        std::cout << x << " is less than or equal to " << y << '\n';
 
    return 0;
}

Và kết quả từ một lần chạy ví dụ:

Enter an integer: 4
Enter another integer: 5
4 does not equal 5
4 is less than 5
4 is less than or equal to 5

Các toán tử này rất dễ sử dụng khi so sánh các số nguyên.

1. Giá trị điều kiện Boolean

Theo mặc định, các điều kiện trong câu lệnh if hoặc toán tử điều kiện (và một số nơi khác) được đánh giá là giá trị Boolean.

Nhiều lập trình viên mới sẽ viết các câu lệnh như sau:

if (b1 == true) ...

Điều này là thừa vì == true không thực sự thêm bất kỳ giá trị nào vào điều kiện. Thay vào đó, chúng ta nên viết:

if (b1) ...

Tương tự như sau:

if (b1 == false) ...

tốt hơn được viết là:

if (!b1) ...

Bạn nên – Đừng thêm == hoặc! = Không cần thiết vào các điều kiện. Nó làm cho chúng khó đọc hơn mà không cung cấp bất kỳ giá trị bổ sung nào.

2. So sánh các giá trị dấu chấm động

Hãy xem xét chương trình sau:

#include <iostream>
 
int main()
{
    double d1{ 100.0 - 99.99 }; // should equal 0.01
    double d2{ 10.0 - 9.99 }; // should equal 0.01
 
    if (d1 == d2)
        std::cout << "d1 == d2" << '\n';
    else if (d1 > d2)
        std::cout << "d1 > d2" << '\n';
    else if (d1 < d2)
        std::cout << "d1 < d2" << '\n';
    
    return 0;
}

Các biến d1 và d2 đều phải có giá trị 0,01. Nhưng chương trình này in ra một kết quả không mong muốn:

d1> d2

Nếu bạn kiểm tra giá trị của d1 và d2 trong trình gỡ lỗi, bạn có thể thấy rằng d1 = 0,0100000000000005116 và d2 = 0,0099999999999997868. Cả hai số đều gần bằng 0,01, nhưng d1 lớn hơn và d2 nhỏ hơn.

Nếu yêu cầu độ chính xác cao, việc so sánh các giá trị dấu chấm động bằng bất kỳ toán tử quan hệ nào có thể nguy hiểm. Điều này là do các giá trị dấu phẩy động không chính xác và các lỗi làm tròn nhỏ trong toán hạng dấu phẩy động có thể gây ra kết quả không mong muốn. Chúng ta đã thảo luận về lỗi làm tròn trong bài  – Số dấu phẩy động nếu bạn cần học thêm.

Khi các toán tử nhỏ hơn và lớn hơn (<, <=,>, và> =) được sử dụng với các giá trị dấu phẩy động, chúng thường sẽ tạo ra câu trả lời đúng (chỉ có khả năng thất bại khi các toán hạng gần giống nhau). Do đó, việc sử dụng các toán tử này với toán hạng dấu phẩy động có thể được chấp nhận, miễn là hậu quả của việc nhận được câu trả lời sai khi các toán hạng giống nhau là nhẹ.

Ví dụ: hãy xem xét một trò chơi (chẳng hạn như Space Invaders) mà bạn muốn xác định xem hai vật thể chuyển động (chẳng hạn như tên lửa và người ngoài hành tinh) có giao nhau hay không. Nếu các đối tượng vẫn ở xa nhau, các toán tử này sẽ trả về câu trả lời đúng. Nếu hai đối tượng cực kỳ gần nhau, bạn có thể nhận được câu trả lời theo cách nào đó. Trong những trường hợp như vậy, câu trả lời sai có thể thậm chí sẽ không được chú ý (nó sẽ giống như một cú đánh trượt hoặc suýt trúng đích) và trò chơi sẽ tiếp tục.

3. So sánh bằng dấu phẩy động

Các toán tử so sánh bằng (== và !=) Rắc rối hơn nhiều. Hãy xem xét toán tử ==, chỉ trả về true nếu các toán hạng của nó chính xác bằng nhau. Bởi vì ngay cả lỗi làm tròn nhỏ nhất cũng sẽ khiến hai số dấu phẩy động không bằng nhau, toán tử == có nguy cơ cao trả về sai khi giá trị đúng có thể được mong đợi. Toán tử != Có cùng một loại vấn đề này.

Vì lý do này, nên tránh sử dụng các toán tử này với các toán hạng dấu phẩy động.

Lưu ý – Tránh sử dụng toán tử == và toán tử! = Với toán hạng dấu phẩy động.

Vì vậy, làm thế nào chúng ta có thể so sánh một cách hợp lý hai toán hạng dấu phẩy động để xem liệu chúng có bằng nhau không?

Phương pháp phổ biến nhất để thực hiện bằng dấu phẩy động liên quan đến việc sử dụng một hàm để xem liệu hai số có gần giống nhau hay không. Nếu chúng “đủ gần”, thì chúng ta gọi chúng là bằng nhau. Giá trị được sử dụng để biểu thị “đủ gần” theo truyền thống được gọi là epsilon. Epsilon thường được định nghĩa là một số dương nhỏ (ví dụ: 0,00000001, đôi khi được viết 1e-8).

Các developer mới thường cố gắng viết hàm “đủ gần” của riêng họ như sau:

#include <cmath> // for std::abs()
 
// epsilon is an absolute value
bool isAlmostEqual(double a, double b, double epsilon)
{
    // if the distance between a and b is less than epsilon, then a and b are "close enough"
    return std::abs(a - b) <= epsilon;
}

std :: abs () là một hàm trong header file <cmath> trả về giá trị tuyệt đối của đối số của nó. Vì vậy, std :: abs (a – b) <= epsilon kiểm tra xem khoảng cách giữa a và b có nhỏ hơn bất kỳ giá trị epsilon nào đại diện cho “đủ gần” được chuyển vào không. Nếu a và b đủ gần, hàm trả về true để chỉ ra chúng bằng nhau. Nếu không, nó trả về false.

Mặc dù hàm này có thể hoạt động, nhưng nó không tuyệt vời. Một epsilon 0,00001 là tốt cho các đầu vào khoảng 1,0, quá lớn cho các đầu vào khoảng 0,0000001 và quá nhỏ cho các đầu vào như 10.000. Điều này có nghĩa là mỗi khi chúng ta gọi hàm này, chúng ta phải chọn một epsilon thích hợp cho đầu vào của chúng ta. Nếu chúng ta biết mình sẽ phải mở rộng epsilon tương ứng với đầu vào của chúng ta, chúng ta cũng có thể sửa đổi hàm để làm điều đó cho chúng ta.

Donald Knuth, một nhà khoa học máy tính nổi tiếng, đã gợi ý phương pháp sau trong cuốn sách “Nghệ thuật lập trình máy tính, Tập II: Thuật toán bán số

#include <cmath> // std::abs
#include <algorithm> // std::max
 
// return true if the difference between a and b is within epsilon percent of the larger of a and b
bool approximatelyEqual(double a, double b, double epsilon)
{
    return (std::abs(a - b) <= (std::max(std::abs(a), std::abs(b)) * epsilon));
}

Trong trường hợp này, thay vì epsilon là một số tuyệt đối, epsilon bây giờ tương đối với độ lớn của a hoặc b.

Hãy xem xét chi tiết hơn cách hoạt động của hàm trông có vẻ điên rồ này. Ở phía bên trái của toán tử <=, std :: abs (a – b) cho chúng ta biết khoảng cách giữa a và b là một số dương.

Ở phía bên phải của toán tử <=, chúng ta cần tính giá trị lớn nhất của “đủ gần” mà chúng ta sẵn sàng chấp nhận. Để làm điều này, thuật toán chọn giá trị lớn hơn của a và b (như một chỉ báo sơ bộ về độ lớn tổng thể của các con số), sau đó nhân nó với epsilon. Trong hàm này, epsilon đại diện cho một tỷ lệ phần trăm. Ví dụ: nếu chúng ta muốn nói “đủ gần” có nghĩa là a và b nằm trong khoảng 1% lớn hơn của a và b, chúng ta chuyển epsilon là 0,01 (1% = 1/100 = 0,01). Giá trị của epsilon có thể được điều chỉnh thành bất kỳ giá trị nào phù hợp nhất cho các trường hợp (ví dụ: epsilon 0,002 có nghĩa là trong khoảng 0,2%).

Để thực hiện bất đẳng thức (! =) Thay vì đẳng thức, chỉ cần gọi hàm này và sử dụng toán tử logic NOT (!) Để lật kết quả:

if (!approximatelyEqual(a, b, 0.001))
    std::cout << a << " is not equal to " << b << '\n';

Lưu ý rằng mặc dù hàm xấp xỉ Equal() sẽ hoạt động trong hầu hết các trường hợp, nhưng nó không hoàn hảo, đặc biệt là khi các số tiến gần đến số 0:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Group: https://www.facebook.com/groups/cafedev.vn/
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
Pinterest: https://www.pinterest.com/cafedevvn/
YouTube: https://www.youtube.com/channel/UCE7zpY_SlHGEgo67pHxqIoA/
*/

#include <algorithm>
#include <iostream>
#include <cmath>
 
// return true if the difference between a and b is within epsilon percent of the larger of a and b
bool approximatelyEqual(double a, double b, double epsilon)
{
	return (std::abs(a - b) <= (std::max(std::abs(a), std::abs(b)) * epsilon));
}
 
int main()
{
	// a is really close to 1.0, but has rounding errors, so it's slightly smaller than 1.0
	double a{ 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 };
 
	// First, let's compare a (almost 1.0) to 1.0.
	std::cout << approximatelyEqual(a, 1.0, 1e-8) << '\n';
 
	// Second, let's compare a-1.0 (almost 0.0) to 0.0
	std::cout << approximatelyEqual(a-1.0, 0.0, 1e-8) << '\n';
}

Có lẽ đáng ngạc nhiên, điều này trả lại:

1
0

Cuộc gọi thứ hai không hoạt động như mong đợi. Toán học chỉ đơn giản là chia nhỏ gần bằng không.

Một cách để tránh điều này là sử dụng cả epsilon tuyệt đối (như chúng ta đã làm trong cách tiếp cận đầu tiên) và epsilon tương đối (như chúng ta đã làm trong cách tiếp cận của Knuth):

// return true if the difference between a and b is less than absEpsilon, or within relEpsilon percent of the larger of a and b
bool approximatelyEqualAbsRel(double a, double b, double absEpsilon, double relEpsilon)
{
    // Check if the numbers are really close -- needed when comparing numbers near zero.
    double diff{ std::abs(a - b) };
    if (diff <= absEpsilon)
        return true;
 
    // Otherwise fall back to Knuth's algorithm
    return (diff <= (std::max(std::abs(a), std::abs(b)) * relEpsilon));
}

Trong thuật toán này, trước tiên chúng ta kiểm tra xem a và b có gần nhau hay không về mặt tuyệt đối, điều này sẽ xử lý trường hợp a và b đều gần bằng không. Tham số abs Epsilon phải được đặt thành một giá trị rất nhỏ (ví dụ: 1e-12). Nếu không thành công, thì chúng ta quay lại thuật toán của Knuth, sử dụng epsilon tương đối.

Đây là code trước đây của chúng ta kiểm tra cả hai thuật toán:

#include <algorithm>
#include <iostream>
#include <cmath>
 
// return true if the difference between a and b is within epsilon percent of the larger of a and b
bool approximatelyEqual(double a, double b, double epsilon)
{
	return (std::abs(a - b) <= (std::max(std::abs(a), std::abs(b)) * epsilon));
}
 
bool approximatelyEqualAbsRel(double a, double b, double absEpsilon, double relEpsilon)
{
    // Check if the numbers are really close -- needed when comparing numbers near zero.
    double diff{ std::abs(a - b) };
    if (diff <= absEpsilon)
        return true;
 
    // Otherwise fall back to Knuth's algorithm
    return (diff <= (std::max(std::abs(a), std::abs(b)) * relEpsilon));
}
 
int main()
{
    // a is really close to 1.0, but has rounding errors
    double a{ 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 };
 
    std::cout << approximatelyEqual(a, 1.0, 1e-8) << '\n';     // compare "almost 1.0" to 1.0
    std::cout << approximatelyEqual(a-1.0, 0.0, 1e-8) << '\n'; // compare "almost 0.0" to 0.0
    std::cout << approximatelyEqualAbsRel(a-1.0, 0.0, 1e-12, 1e-8) << '\n'; // compare "almost 0.0" to 0.0
}
1
0
1

Bạn có thể thấy rằng với một absEpsilon được chọn phù hợp, xấp xỉ EqualAbsRel () sẽ xử lý các đầu vào nhỏ một cách chính xác.

So sánh các số dấu phẩy động là một chủ đề khó và không có thuật toán “một kích thước phù hợp với tất cả” hoạt động cho mọi trường hợp. Tuy nhiên, xấp xỉ EqualAbsRel() phải đủ tốt để xử lý hầu hết các trường hợp bạn sẽ gặp phải.

Cài ứng dụng cafedev để dễ dàng cập nhật tin và học lập trình mọi lúc mọi nơi tại đây.

Nguồn và Tài liệu tiếng anh tham khảo:

Tài liệu từ cafedev:

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!