避免代码重复定义比较运算符 `<, <=, >, >=, ==, !=` 的最佳方法,但考虑到NaN的情况该怎么办?

8
在数学中,x <= y 相当于 !(x > y)。这对于浮点数算术在大多数情况下都是正确的,但并非总是如此。当 xy 是 NaN 时,x <= y 不等同于 !(x > y),因为将 NaN 与任何值进行比较始终返回 false。但仍然有 x <= y <=> !(x > y) 大部分情况下是真的。
现在,假设我正在编写一个包含浮点值的类,并且我想为这个类定义比较运算符。为了明确起见,假设我正在编写一个高精度浮点数,它内部使用一个或多个 double 值来存储高精度数字,从数学上讲,这个类的 x < y 的定义已经定义了所有其他运算符(如果我遵循比较运算符的常规语义)。但是,NaN 打破了这种数学上的美好。所以也许我不得不单独编写许多这些运算符,以考虑到 NaN。但有没有更好的方法呢?我的问题是:我如何尽可能避免代码重复,同时仍然尊重 NaN 的行为? 相关:http://www.boost.org/doc/libs/1_59_0/libs/utility/operators.htm。boost / operators 如何解决这个问题?
注意:我将此问题标记为 c++ ,因为那是我理解的语言。请在该语言中编写示例。

相关:https://dev59.com/54nca4cB1Zd3GeqP6BZV#29269216。该答案提供了适用于整数类型的令人满意的解决方案。但由于NaN,它不适用于浮点数。 - a06e
2个回答

1
个人而言,我会使用类似 this answer 中的技术,该技术基于 operator<() 定义比较函数,生成一个严格弱序。对于具有空值的类型,其比较操作总是返回 false,这些操作将以 operator<() 为基础定义,提供所有非空值上的严格弱序和一个 is_null() 测试。
例如,代码可能如下所示:
namespace nullable_relational {
    struct tag {};

    template <typename T>
    bool non_null(T const& lhs, T const& rhs) {
        return !is_null(lhs) && !is_null(rhs);
    }

    template <typename T>
    bool operator== (T const& lhs, T const& rhs) {
        return non_null(lhs, rhs) && !(rhs < lhs) && !(lhs < rhs);
    }
    template <typename T>
    bool operator!= (T const& lhs, T const& rhs) {
        return non_null(lhs, rhs) || !(lhs == rhs);
    }

    template <typename T>
    bool operator> (T const& lhs, T const& rhs) {
        return non_null(lhs, rhs) && rhs < lhs;
    }
    template <typename T>
    bool operator<= (T const& lhs, T const& rhs) {
        return non_null(lhs, rhs) && !(rhs < lhs);
    }
    template <typename T>
    bool operator>= (T const& lhs, T const& rhs) {
        return non_null(lhs, rhs) && !(lhs < rhs);
    }
}

这将会像这样使用:

它将被用作以下目的:

#include <cmath>
class foo
    : private nullable_relational::tag {
    double value;
public:
    foo(double value): value(value) {}
    bool is_null() const { return std::isnan(this->value); }
    bool operator< (foo const& other) const { return this->value < other.value; }
};
bool is_null(foo const& value) { return value.is_null(); }

同一主题的变体可能是使用一个比较函数的实现,该函数由比较函数参数化,并负责适当地向比较函数提供参数。例如:
namespace compare_relational {
    struct tag {};

    template <typename T>
    bool operator== (T const& lhs, T const& rhs) {
        return compare(lhs, rhs, [](auto&& lhs, auto&& rhs){ return lhs == rhs; });
    }
    template <typename T>
    bool operator!= (T const& lhs, T const& rhs) {
        return compare(lhs, rhs, [](auto&& lhs, auto&& rhs){ return lhs != rhs; });
    }

    template <typename T>
    bool operator< (T const& lhs, T const& rhs) {
        return compare(lhs, rhs, [](auto&& lhs, auto&& rhs){ return lhs < rhs; });
    }
    template <typename T>
    bool operator> (T const& lhs, T const& rhs) {
        return compare(lhs, rhs, [](auto&& lhs, auto&& rhs){ return lhs > rhs; });
    }
    template <typename T>
    bool operator<= (T const& lhs, T const& rhs) {
        return compare(lhs, rhs, [](auto&& lhs, auto&& rhs){ return lhs <= rhs; });
    }
    template <typename T>
    bool operator>= (T const& lhs, T const& rhs) {
        return compare(lhs, rhs, [](auto&& lhs, auto&& rhs){ return lhs >= rhs; });
    }
}

class foo
    : private compare_relational::tag {
    double value;
public:
    foo(double value): value(value) {}

    template <typename Compare>
    friend bool compare(foo const& f0, foo const& f1, Compare&& predicate) {
        return predicate(f0.value, f1.value);
    }
};

我可以想象拥有多个这些操作生成的命名空间,以支持适当的常见情况选择。另一个选项可能是不同于浮点数的排序方式,并且例如将空值视为最小或最大值。由于一些人使用NaN-boxing,因此甚至提供不同NaN值的顺序并将NaN值排列在适当的位置可能是合理的。例如,使用底层位表示提供浮点值的总序,虽然该顺序可能与operator<()创建的顺序不同,但可能适用于将对象用作有序容器中的键。

0

当您定义operator<时,需要处理NaN情况。为了排序和比较的目的,如果将NaN视为小于任何非NaN,则可以执行以下操作:

bool operator<(double l, double r) {
    if (isnan(l)) {
        if (isnan(r)) return false; // NaN == NaN
        return true;        // NaN < rational
    }
    return l < r;       // if r is NaN will return false, which is how we've defined it
}

其他运算符都是基于 operator< 定义的,不需要手动编写代码。


但是 NaN < x 应该始终为 false - a06e
你想让 x > Nan 成为 false 还是 true?为了保持和 < 的对称性,应该是 true。 - 1201ProgramAlarm
@1201程序警报 NaN op <任何内容> 始终为 false。 - Mark B
@Mark B 我知道这一点,问题描述看起来像是 OP 在更大的上下文中使用了双精度浮点数,并且需要处理其中的 NaN 值。所以也许我不太清楚 OP 在问什么。 - 1201ProgramAlarm
@1201ProgramAlarm 我想在我的类中复现通常的约定,即 NaN < xx < NaN 始终为 false - a06e
好的,那不是我最初从你的问题中理解的。鉴于此,我的解决方案与@Dietmar Kühl非常相似,其中每个运算符都检查NaN,但我将使用可以返回小于、等于或大于(如strcmp)的比较,以避免多次进行相等比较。 - 1201ProgramAlarm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接