C++关系运算符与浮点数比较指南:精确性与最佳实践

关系运算符基础知识

关系运算符允许你比较两个值。共有 6 个关系运算符:

运算符符号形式操作
大于>x > y如果 x 大于 y 则为 true,否则为 false
小于<x < y如果 x 小于 y 则为 true,否则为 false
大于等于>=x >= y如果 x 大于或等于 y 则为 true,否则为 false
小于等于<=x <= y如果 x 小于或等于 y 则为 true,否则为 false
等于==x == y如果 x 等于 y 则为 true,否则为 false
不等于!=x != y如果 x 不等于 y 则为 true,否则为 false

这些运算符的工作方式大部分你已经见过,并且相当直观。每个运算符都会求值为布尔值 true (1) 或 false (0)。

关系运算符示例

以下是一个使用这些运算符与整数的示例代码:

#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;
}

运行结果示例:

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

在比较整数时,这些运算符的使用极其简单直接。

布尔条件值使用指南

默认情况下,if 语句、条件运算符(以及其他一些地方)中的条件会求值为布尔值。

常见的布尔条件写法

许多新程序员会这样写语句:

if (b1 == true) ...

这是冗余的,因为 == true 实际上并没有为条件增加任何价值。相反,我们应该写成:

if (b1) ...

类似地,下面的写法:

if (b1 == false) ...

最好写成:

if (!b1) ...

最佳实践

避免在条件中添加不必要的 ==!=。这会使它们更难阅读,却不会提供任何额外价值。

浮点数比较的挑战与解决方案

浮点数比较的基本问题

考虑以下程序:

#include <iostream>

int main()
{
    constexpr double d1{ 100.0 - 99.99 }; // 数学上应该等于 0.01
    constexpr double d2{ 10.0 - 9.99 };   // 数学上应该等于 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;
}

浮点数比较的特殊情况

小于和大于比较

当小于 (<)、大于 (>)、小于等于 (<=) 和大于等于 (>=) 运算符用于浮点值时,在大多数情况下(当操作数的值不相似时)它们会产生可靠的结果。然而,如果操作数几乎相同,这些运算符应被视为不可靠

等于和不等于比较

等于运算符 (==!=) 则要麻烦得多。考虑以下示例:

#include <iostream>

int main()
{
    std::cout << std::boolalpha << (0.3 == 0.2 + 0.1); // 打印 false
    return 0;
}

浮点数比较的最佳实践

基本的近似相等比较

#include <cmath> // 为了 std::abs()

bool approximatelyEqualAbs(double a, double b, double absEpsilon)
{
    return std::abs(a - b) <= absEpsilon;
}

Knuth算法实现

#include <algorithm> // 为了 std::max
#include <cmath>     // 为了 std::abs

bool approximatelyEqualRel(double a, double b, double relEpsilon)
{
    return (std::abs(a - b) <= (std::max(std::abs(a), std::abs(b)) * relEpsilon));
}

综合解决方案

bool approximatelyEqualAbsRel(double a, double b, double absEpsilon, double relEpsilon)
{
    if (std::abs(a - b) <= absEpsilon)
        return true;
    return approximatelyEqualRel(a, b, relEpsilon);
}

C++23中的constexpr支持

现代C++实现

// C++23 版本
#include <algorithm>
#include <cmath>

constexpr bool approximatelyEqualRel(double a, double b, double relEpsilon)
{
    return (std::abs(a - b) <= (std::max(std::abs(a), std::abs(b)) * relEpsilon));
}

constexpr bool approximatelyEqualAbsRel(double a, double b, double absEpsilon, double relEpsilon)
{
    if (std::abs(a - b) <= absEpsilon)
        return true;
    return approximatelyEqualRel(a, b, relEpsilon);
}

C++14/17/20的替代方案

#include <algorithm>
#include <iostream>

template <typename T>
constexpr T constAbs(T x)
{
    return (x < 0 ? -x : x);
}

constexpr bool approximatelyEqualRel(double a, double b, double relEpsilon)
{
    return (constAbs(a - b) <= (std::max(constAbs(a), constAbs(b)) * relEpsilon));
}

constexpr bool approximatelyEqualAbsRel(double a, double b, double absEpsilon, double relEpsilon)
{
    if (constAbs(a - b) <= absEpsilon)
        return true;
    return approximatelyEqualRel(a, b, relEpsilon);
}

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"