C++ IEEE浮点数对于std::map和std::set是否有效? 背景
对关联容器的键类型(例如std::map)的比较器的要求是,它对键类型的元素施加严格的弱顺序 对于给定的比较器C++ IEEE浮点数对于std::map和std::set是否有效? 背景,c++,map,set,ieee-754,C++,Map,Set,Ieee 754,对关联容器的键类型(例如std::map)的比较器的要求是,它对键类型的元素施加严格的弱顺序 对于给定的比较器comp(x,y)我们定义equiv(x,y)=!公司(x,y)&!comp(y,x) comp(x,y)是严格弱序的要求如下 灵活性(!comp(x,x)适用于所有x) 排序的传递性(如果comp(a,b)和comp(b,c)那么comp(a,c)) 等价的传递性(如果等价(a,b)和等价(b,c)那么等价(a,c)) std::less(默认的比较器)使用操作符简言之:这很好(从您的
comp(x,y)
我们定义equiv(x,y)=!公司(x,y)&!comp(y,x)
comp(x,y)
是严格弱序的要求如下
!comp(x,x)
适用于所有x
)comp(a,b)
和comp(b,c)
那么comp(a,c)
)等价(a,b)
和等价(b,c)
那么等价(a,c)
)std::less
(默认的比较器)使用操作符简言之:这很好(从您的问题的意义上讲)
如果您阻止不满足排序要求的值(即,NaN
),则行为将完全定义。将浮点作为关联容器的键有时是个坏主意,因为相等语义非常差。但这取决于你想做什么。请记住,NaN和无穷大通常不是问题。您可以使用特殊的比较器函数来处理它们(我通常不这样做),很明显,标准的要求是关于将在容器中结束的实际键,您可以将其视为键类型的子集。映射实现永远不会引入您没有将自己输入映射的关键实例
我曾经对一个映射使用过这个谓词,其中我可以禁止两个非常接近的值:
struct FuzzyComparer
{
template <typename T>
bool operator()(T x, T y) const
{
static const T oneMinusEps = (T)1. - 64 * std::numeric_limits<T>::epsilon();
return x / y < oneMinusEps;
}
};
struct FuzzyComparer
{
模板
布尔运算符()(tx,tyconst)
{
静态常数T oneMinusEps=(T)1.-64*std::numeric_limits::epsilon();
返回x/y<一个最小值;
}
};
这并不能为您提供良好的等价关系。这仅在您希望存储离散浮点值,并且准备在计算中容忍某种舍入错误时才有用,这种舍入错误会产生要检索的键对于将插入的实际键,它会产生一个等价关系
您将无法在浮点数上找到与算术运算兼容的良好等价关系,即使加法和乘法相关联
您要么必须扔掉“等价关系”部分,这在现实世界的代码中应该不是什么大问题(我怀疑eq.关系的及物性在典型的映射实现中的使用程度会让您感到不安),要么扔掉与算术运算的兼容性。但是,使用浮点值作为键有什么意义呢?我怀疑这些限制应该被视为是指关系在实际用作键的值上的行为,而不一定是在该类型的所有值上。目前没有时间浏览标准,寻找引用实际容器元素而不是类型的所有值的“冒烟的枪”语言
类似的情况:如果一个比较器(用于指针或智能指针的容器)调用一个虚拟函数,并且有人链接了它所比较的类型的派生类,该类以一种使比较器不是严格弱序的方式重写虚拟函数,该怎么办?即使没有人实际使用该派生类,程序也会变得未定义吗
如果有疑问,您可以使用严格弱顺序的比较器来支持NaN:
bool operator()(double a, double b) {
if ((a == a) && (b == b)) {
return a < b;
}
if ((a != a) && (b != b)) return false;
// We have one NaN and one non-NaN.
// Let's say NaN is less than everything
return (a != a)
}
这引发了另一个问题——显然有人可以创建一个SaneDouble
,然后将其值设置为NaN(假设实现允许他们从某处获得一个而不会崩溃)。那么,“SaneDouble元素”是否严格弱有序呢?我半心半意地试图在构造函数中创建一个类不变量,这是否会导致我的程序未定义,即使实际上没有人破坏该不变量,仅仅因为他们可以,因此这样做的结果是“SaneDouble的元素”?当且仅当value
标记为private
时,才定义程序的行为,这真的是标准的意图吗?标准是否真正定义了类型的“元素”是什么
我想知道我们是否应该将“键元素”解释为比较器对键的某些元素产生严格的弱序。大概包括实际使用的。“我有甜甜圈”并不意味着我有所有的甜甜圈。不过,这是一个延伸。为了正确排序,可以将浮点和双精度浮点作为std::map或std::set的键
问题是当涉及到唯一性时,由于浮点数和双精度的比较方式,很可能会出现重复
使用基于ε的比较(接近值视为相等)也不是没有危险的,因为您可能会因为太接近而消除真正的非重复项
如果使用简单的“find”,那么“lookup”可能找不到存在的元素,因此您可能希望在x-delta上使用某种epsilon查找,其中x是您真正要查找的,并且允许小于x+delta的值
综上所述,在std::multiset或std::multimap中使用float或double作为键显然没有问题,只要使用上界搜索而不是等距搜索
关于NAN,如果集合或映射不是空的,则它们将被视为与已经存在的任何元素“相等”,因此不会插入。如果先插入NaN,则所有后续插入都将失败
它们被认为是相等的,因为xNice比较器。人们太经常使用差异而不是融洽,这对“极端”价值不起作用……这是否提供了一个
struct SaneDouble {
double value;
SaneDouble (double d) : value(d) {
if (d != d) throw std::logic_error();
}
static friend bool operator<(SaneDouble lhs, SaneDouble rhs) {
return lhs.value < rhs.value;
}
// possibly a conversion to double
};