Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to avoid code duplication defining comparison operators `<, <=, >, >=, ==, !=`, but taking into account NaNs?

I mathematics, x <= y is equivalent to !(x > y). This is true for floating-point arithmetic, in most cases, but not always. When x or y is NaN, x <= y is not equivalent to !(x > y), because comparing a NaN to anything always returns false. But still, x <= y <=> !(x > y) is true most of the time.

Now, suppose I am writing a class that contains floating-point values, and I want to define comparison operators for this class. For definiteness, suppose I am writing a high-precision floating-point number, which uses one or more double values internally to store the high-precision number. Mathematically, the definition of x < y for this class already defines all the other operators (if I am being consistent with the usual semantics of the comparison operators). But NaNs break this mathematical nicety. So maybe I am forced to write many of these operators separately, just to take into account NaNs. But is there a better way? My question is: How can I avoid code duplication as much as possible and still respect the behavior of NaN?

Related: http://www.boost.org/doc/libs/1_59_0/libs/utility/operators.htm. How does boost/operators resolve this issue?

Note: I tagged this question c++ because that's what I understand. Please write examples in that language.

like image 997
becko Avatar asked Aug 16 '15 20:08

becko


People also ask

Which of the following is not a comparison operator in C++ language <= ==?

Explanation: The operator =! Is not the comparison operator.

Which comparison operator can be used to compare two values?

Comparison operators — operators that compare values and return true or false . The operators include: > , < , >= , <= , === , and !== . Logical operators — operators that combine multiple boolean expressions or values and provide a single boolean output.

How do you define a comparison operator in C++?

Comparison operators in C++ are the ones that are there to compare two values with each other such as “==”, “!= ”, “>”, “<”, “>=”, and “<=”. This article will share the methods of overloading all six of these comparison operators in C++ in Ubuntu 20.04.


1 Answers

Personally I would use a similar technique as in this answer which defines comparison function based on operator<() yielding a strict weak order. For types with a null value which is meant to have comparisons always yield false the operations would be defined in terms of operator<() providing a strict weak order on all non-null value and an is_null() test.

For example, the code could look like this:

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

It would be use like this:

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

A variation of the same theme could be an implementation in terms of one compare function which is parameterized by the comparison function and which is responsible to appropriately feed the comparison function with parameters. For example:

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

I could imagine having multiple of these operation-generating namespaces to support a suitable choice for common situations. Another option could be a different ordering than floating points and, e.g., consider a null value the smallest or the largest value. Since some people use NaN-boxing it may even be reasonable to provide an order on different NaN values and arrange the NaN values at suitable places. For example, using the underlying bit representation provide a total order of floating point values which may be suitable for using the objects as a key in an ordered container although the order might be different from the order created by operator<().

like image 53
Dietmar Kühl Avatar answered Oct 04 '22 21:10

Dietmar Kühl