考虑使用double
上的C ++中的有序和无序关联容器。
NaN
是否为有效密钥类型?
对于有序的容器,我应该说“不”,因为它不尊重严格的弱序。
对于无序容器,我不知道。
以下是GCC 4.6.2中的情况:
#include <map>
#include <unordered_map>
#include <cmath>
#include <iostream>
#include <prettyprint.hpp>
int main()
{
typedef std::map<double, int> map_type; // replace by "unorderd_map"
map_type dm;
double d = std::acos(5); // a good nan
dm[d] = 2;
dm[d] = 5;
dm[d] = 7;
std::cout << "dm[NaN] = " << dm[d] << ", dm = " << dm << std::endl;
}
对于有序地图,我得到:
dm[NaN] = 7, dm = [(nan, 7)]
对于无序地图,我得到:
dm[NaN] = 0, dm = [(nan, 0), (nan, 7), (nan, 5), (nan, 2)]
因此,在有序地图中,所有NaN都被视为相同,这是我所期望的,尽管NaN似乎违反了要求。然而,对于无序地图,我再也无法检索元素,并且所有NaN都不同。这也不是我所期望的。
标准是否必须就此事发表任何意见?
更新:由于下面的答案很好,请注意,如果您在其中插入了其他任何,std::map
将会中断。
(我非常感谢其他语言如何处理关联容器中的浮点键。)
答案 0 :(得分:18)
标准禁止使用它们。
对于(有序)关联容器,严格弱序(25.4 / 4)的定义表示:
如果我们将
equiv(a, b)
定义为!comp(a, b) && !comp(b, a)
,那么 要求是comp
和equiv
都是传递关系......equiv(a, b) && equiv(b, c)
隐含equiv(a, c)
a = 0.0,b = NaN,c = 1.0,comp = std::less<double>()
对于无序容器,23.2.5 / 3表示等式谓词Pred
“在类型Key
的值上引起等价关系”。等价关系是自反的,std::equal_to<double>()(NaN,NaN)
是假的,所以equal_to<double>()
不是等价关系。
顺便说一下,在双精度上键入容器有点可怕,就像比较双精度相等总是有些可怕一样。你永远不会知道你最不重要的是什么。
我一直认为有点奇怪的是标准表达了关键类型方面的要求,而不是根据添加到容器的实际键值。我相信你可以选择阅读这一点,因为如果实现支持NaN,无论是否实际向某个实例添加NaN,都不能保证map<double, int>
已经定义了行为。但实际上,std::map
的实现不能以某种方式从后袋中召唤NaN
并试图比较它,它只会比较传递给实例的键值。所以它应该没问题(如果有点吓人),你可以避免添加NaN。
我非常感谢有关其他语言如何处理的评论 关联容器中的浮点键
Python中的一些快速实验(其中set
和dict
是无序的关联容器,通过引用保存键和值)表明NaN被视为价值不相等的对象,即使它们'重新“相同的NaN”,但同样的nan 对象可以通过身份再次找到。据我所见,容器似乎不会被包含多个nans或nans和其他值的混合物所扰乱:
>>> thing = set()
>>> nan = float('nan')
>>> nan
nan
>>> thing.add(nan)
>>> thing.add(nan)
>>> thing
set([nan])
>>> thing = dict()
>>> thing[nan] = 1
>>> thing[nan] = 2
>>> thing[nan]
2
>>> nan2 = float('nan')
>>> thing[nan2] = 3
>>> thing
{nan: 2, nan: 3}
>>> thing = set()
>>> thing.add(nan)
>>> thing.add(nan2)
>>> thing
set([nan, nan])
>>> thing = dict()
>>> thing[nan] = 1
>>> thing[nan2] = 2
>>> thing[0] = 3
>>> thing
{nan: 1, nan: 2, 0: 3}
>>> thing.keys()
[nan, nan, 0]
>>> thing.values()
[1, 2, 3]
>>> thing[0]
3
>>> thing[1]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 1
答案 1 :(得分:3)
NaN
可以存储在地图中 - 也就是说,它们是可复制的,并且可以比较。 std::less
对于双打不符合地图对严格弱排序的要求,因此您在技术上得到了未定义的行为。但是,即使标准不要求,也很容易解释这种行为。 Map使用等价而不是相等来确定项是否重复。两个NaN比较等效,但不相等。但是,在某些情况下会崩溃。例如,如果您尝试将除 NaN之外的内容插入到该地图中,则会将其视为与NaN等效,并且您不会获得插入内容。尝试添加一些除NaN之外的实数,你也可以看到地图分解。
散列行为是预期的,但也没有为散列表定义 - 散列表要求其内容可复制构造且相等。多个NaN的哈希比较相等,所以它们都会进入同一个桶,但是哈希表使用相等比较而不是比较比较(相等而不是等价)。因此,没有一个NaN比较相等,并且您获得该键的多个插入。这是因为NaN打破了哈希表的相等可比性要求 - 即std :: equal_to(x,x)为true的不变量。