I数学,x <= y等价于!(x > y)。在大多数情况下,浮点算法是这样的,但并不总是如此。当x或y是NaN时,x <= y是而不是!(x > y),因为将NaN与任何事物进行比较总是返回false。但是,x <= y <=> !(x > y)在大多数情况下都是真实的。
现在,假设我正在编写一个包含浮点值的类,并且我想为这个类定义比较运算符。对于确定性,假设我正在编写一个高精度浮点数,它在内部使用一个或多个double值来存储高精度数字。从数学上讲,该类的x < y定义已经定义了所有其他运算符(如果我与比较运算符的通常语义一致的话)。但是NaN破坏了这个数学的精确性。所以,也许我不得不把这些运算符中的许多分别写出来,只是为了考虑到NaNs。但还有更好的方法吗?我的问题是:如何尽可能避免代码重复,同时仍然尊重NaN__的行为?
相关:0/libs/utility/operators.htm.boost/operators如何解决这个问题?
注:我标记这个问题c++,因为这是我所理解的。请用那种语言写例子。
发布于 2015-08-16 23:25:47
就我个人而言,我将使用类似于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装箱,所以对不同的NaN值提供订单并将NaN值安排在合适的位置可能是合理的。例如,使用基础位表示提供浮点值的总顺序,虽然顺序可能与operator<()创建的顺序不同,但它可能适合于将对象用作有序容器中的键。
发布于 2015-08-16 21:44:36
定义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<定义的,不需要手动编写代码。
https://stackoverflow.com/questions/32039711
复制相似问题