C++ IEEE浮点数对于std::map和std::set是否有效? 背景

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(默认的比较器)使用操作符简言之:这很好(从您的

对关联容器的键类型(例如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
    (默认的比较器)使用
    操作符简言之:这很好(从您的问题的意义上讲)


    如果您阻止不满足排序要求的值(即,
    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
    };