首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >对于std::map和std::set,IEEE浮点数是有效的键类型吗?

对于std::map和std::set,IEEE浮点数是有效的键类型吗?
EN

Stack Overflow用户
提问于 2011-01-27 20:14:54
回答 4查看 3.6K关注 0票数 22

背景

关联容器的键类型(例如std::map)上的比较器的要求是,它对键类型的元素施加了严格的弱顺序。

对于给定的比较器comp(x, y),我们定义equiv(x, y) = !comp(x, y) && !comp(y, x)

comp(x, y)是严格弱顺序的要求是

  1. Irreflexibility (对于排序的所有x)
  2. Transitivity,则为comp(a, c)).
  3. Transitivity (如果为equiv(a, c))

,则为!comp(x, x));如果为comp(a, b),则为comp(b, c);如果为comp(b, c),则为等价(如果为equiv(a, b),则为equiv(b, c)

std::less<float> (默认比较器)使用operator<,它不会因为NaN而创建严格的弱顺序。由于x < NaNNaN < x对于所有x都为false,因此NaN等同于此比较器下的所有浮点数,这违反了条件#3:equiv(1.0, NaN)equiv(NaN, 2.0),但不是equiv(1.0, 2.0)。对于除NaN之外的IEEE浮点数,它是一个严格的弱顺序(其中,除了0-0之外,每个数字都有它自己的等价类)。

问题是

这是否意味着,即使我确保NaN永远不会被插入到容器中,这是否意味着C++标准不允许将IEEE浮点数(和(长)双精度)用作关联容器中的键类型?我不太确定标准中的“Key元素”的措辞--它是指所有可能的元素,还是仅指最终在容器中的元素。

注意:问题不是关于wrt的问题。截断/舍入,我可能很快就会发布一个关于这个问题的不同问题。

更新:

叹一口气。我应该在没有指定float的情况下提出这个问题,我只是觉得这是一个很好的例子。

真正的问题是:是否允许使用只对放入容器的元素施加严格弱顺序的比较器,而不是键类型的所有可能实例?请不要只回答“是”或“不是”,我希望参考一些关于这方面的标准/先前的讨论/一个委员会成员或其他人的回答。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2011-01-27 20:41:23

我怀疑这些限制应该被看作是引用关系在实际用作键的值上的行为,而不一定是在类型的所有值上。现在没有时间去检查标准,寻找引用实际容器元素而不是所有类型值的“确凿证据”语言。

类似的情况:如果比较器(用于指针或智能指针的容器)调用一个虚函数,并且有人链接了它所比较的类型的派生类,这将以一种使比较器不是严格弱顺序的方式重写虚函数?即使没有人实际使用过派生类,程序也会变得未定义吗?

如果有疑问,您可以使用严格弱顺序的比较器来支持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)
}

最后两行“优化”为return (b == b);,尽管我不确定注释是否会随之优化。

我想Tomalak已经说服了我,这个语言确实说整个类型都需要排序。

这没有什么意义,因为映射不会凭空产生值,它只使用给定的值(以及值的副本),但问题在于规则,据我所知,它们就是规则。C++0x也是一样的。我想知道是否有一份缺陷报告,或者任何提交一个的点。

同样令人恼火的是,在(非常少见的)系统中,std::less的指针速度很慢,即使知道指针都指向同一数组的元素,也不能使用<作为指针映射中的比较器。真丢人。

另一种选择是使用以下类作为键类型,以便仅在进入映射时检查键的NaN,而不是像上面那样在每次比较时检查键。

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

这就提出了另一个问题--显然,有人可以创建一个SaneDouble,然后将其value设置为NaN (假设实现允许他们从某个地方获得一个而不会崩溃)。那么“SaneDouble元素”是否是严格的弱有序的呢?我在构造函数中创建类不变量的半心半意的尝试是否使我的程序没有定义,即使实际上没有人破坏不变量,仅仅是因为他们可以这样做,因此这样做的结果是“SaneDouble的元素”?当且仅当value被标记为private时,该标准是否真的打算定义程序的行为?该标准是否真正定义了类型的“元素”是什么?

我想知道我们是否应该将“键的元素”解释为比较器在键的某些元素上诱导出严格的弱顺序。大概包括实际使用的那些。“我有甜甜圈”并不意味着我拥有所有的甜甜圈。不过,这是一种延伸。

票数 9
EN

Stack Overflow用户

发布于 2011-01-27 20:17:48

简而言之:这很好(从你的问题是关于的意义上说)。

如果您阻止不满足排序要求的值(即NaN),则行为是完全定义的。

票数 2
EN

Stack Overflow用户

发布于 2011-01-27 20:19:10

将浮点数作为关联容器的键有时不是一个好主意,因为相等性语义非常差。但这取决于你想做什么。请记住,NaN和无穷大通常不是问题。您可以使用特殊的比较器函数来处理它们(我通常不会这样做),而且很明显,标准的要求是关于将在容器中结束的实际键,您可以将其视为键类型的子集。map实现永远不会引入您自己没有输入到map中的关键实例。

我对一个map使用了once这个谓词,其中我可以禁止两个非常接近的值:

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

这不会为您提供良好的等价关系。只有当您想要存储离散的浮点值,并且您准备好容忍计算中的一些舍入误差时,这才是有用的,因为它会产生您想要检索的键。对于将被插入的实际密钥,它会产生一个等价关系。

你将不会能够提出一个良好的等价关系的浮点数,这是兼容的算术运算,即。with使加法和乘法相关联。

你要么丢弃“等价关系”部分,这在现实世界的代码中不应该是那么大的问题(我怀疑eq的传递性。关系的使用在某种程度上会让你在典型的map实现中感到困扰),或者抛弃与算术运算的兼容性。但是使用浮点值作为键又有什么意义呢?

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/4816156

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档