避免代码重复的最佳方法是定义比较运算符“<、<=、>、>=、==、!=”,但要考虑 NaN?

2023-12-30

我数学,x <= y相当于!(x > y)。对于浮点运算来说也是如此,在多数情况下, 但不总是。什么时候x or y是 NaN,x <= y is not相当于!(x > y),因为比较NaN任何事物总会有回报false。但仍然,x <= y <=> !(x > y)大多数时候都是如此。

现在,假设我正在编写一个包含浮点值的类,并且我想为该类定义比较运算符。为了明确起见,假设我正在编写一个高精度浮点数,它使用一个或多个doublevalue 内部存储高精度数字。从数学上来说,定义x < y因为这个类已经定义了所有其他运算符(如果我与比较运算符的通常语义一致)。但NaN我们打破了这个数学上的精确性。因此,也许我被迫单独编写其中许多运算符,只是为了考虑 NaN。但还有更好的办法吗?我的问题是:如何尽可能避免代码重复并仍然尊重NaN?

有关的:http://www.boost.org/doc/libs/1_59_0/libs/utility/operators.htm http://www.boost.org/doc/libs/1_59_0/libs/utility/operators.htm。 boost/operator 如何解决这个问题?

注:我标记了这个问题c++因为这就是我的理解。请用该语言写出示例。


就我个人而言,我会使用类似的技术这个答案 https://stackoverflow.com/a/29269216/1120273它定义了基于的比较函数operator<()产生严格的弱秩序。对于具有 null 值的类型,这意味着比较始终会产生结果false这些操作将被定义为operator<()对所有非空值提供严格的弱顺序is_null() test.

例如,代码可能如下所示:

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 装箱,因此提供不同 NaN 值的顺序并将 NaN 值排列在合适的位置甚至可能是合理的。例如,使用底层位表示提供浮点值的总顺序,这可能适合使用对象作为有序容器中的键,尽管该顺序可能与由创建的顺序不同operator<().

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

避免代码重复的最佳方法是定义比较运算符“<、<=、>、>=、==、!=”,但要考虑 NaN? 的相关文章

随机推荐