在std :: unordered_set <double>中插入多个非数字

时间:2018-06-26 12:04:31

标签: c++ c++11 ieee-754

当插入非数字元素(std::unordered_set<double>)时,NAN的非直观行为是IEEE 754标准的后果之一。

由于NAN!=NAN遵循以下顺序:

#include <iostream>
#include <cmath>
#include <unordered_set>

int main(){
    std::unordered_set<double> set;
    set.insert(NAN);
    set.insert(NAN);
    std::cout<<"Number of elements "<<set.size()<<"\n";  //there are 2 elements!
}

setsee it live)中有两个元素:NANNAN

与我有关的主要问题是,将N NAN s插入哈希集中时,它们都命中了相同的哈希值,并且N的性能插入哈希集会退化为最坏的运行时间-O(N^2)

例如,请参阅问题末尾的清单或here live-插入NAN所花的时间要比“普通”浮点数多几个数量级。

我的问题:是否可以(如果是-如何)以这样一种方式来调整std::unordered_set<double>,以使集合中最多包含一个NAN元素,而无论插入了NAN个(NAN,-NAN等)?


列表:

#include <iostream>
#include <cmath>
#include <unordered_set>
#include <chrono>

constexpr int N=5000;
void test_insert(double value)
{
    std::unordered_set<double> s;
    auto begin = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < N; i++) {
        s.insert(value);
    }
    auto end = std::chrono::high_resolution_clock::now();
    std::cout << "Duration: " << (std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count() / 1e9) << "\n";
    std::cout << "Number of elements: "<<s.size()<<"\n";
}

int main(){
    std::cout << "Not NAN\n";
    test_insert(1.0);           //takes 0.0001 s
    std::cout << "NAN\n";
    test_insert(NAN);           //takes 0.2 s
}

2 个答案:

答案 0 :(得分:7)

您可以定义自己的谓词以比较键,并为此确保NaN比较相等。可以将其作为std::unordered_set类模板的第三个参数。

请参见下面的EqualPred定义(从问题中复制并修改的代码),及其在声明unordered_set变量中的使用。我在https://en.cppreference.com/w/cpp/container/unordered_set

的文档中使用了第二个参数的默认值

实时演示:http://coliru.stacked-crooked.com/a/7085936431e6698f

#include <iostream>
#include <cmath>
#include <unordered_set>
#include <chrono>

struct EqualPred
{
    constexpr bool operator()(const double& lhs, const double& rhs) const
    {
        if (std::isnan(lhs) && std::isnan(rhs)) return true;
        return lhs == rhs;
    }
};

constexpr int N=5000;
void test_insert(double value)
{
    std::unordered_set<double, std::hash<double>, EqualPred> s;
    auto begin = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < N; i++) {
        s.insert(value);
    }
    auto end = std::chrono::high_resolution_clock::now();
    std::cout << "Duration: " << (std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count() / 1e9) << "\n";
    std::cout << "Number of elements: "<<s.size()<<"\n";
}

int main(){
    std::cout << "Not NAN\n";
    test_insert(1.0);           //takes 0.0001 s
    std::cout << "NAN\n";
    test_insert(NAN);           //takes 0.2 s
}

值得注意的是(由于@ead的评论)-NaN+NaN可能会散列为不同的值。如果要以相同的方式处理它们,则需要提供第二个模板参数(哈希函数)的不同实现。这应该检测到任何NaN并每次都对相同的NaN进行哈希处理。

答案 1 :(得分:3)

从您对安德鲁斯答案的评论中,

  

我认为此解决方案存在问题:-NAN将具有与NAN不同的哈希值,但对于哈希函数h必须成立:如果x == y,则h(x)== h(y)

这对散列的处理方式有所不同,因此,如果需要"`column_name`" ...

,则还需要定义自己的散列函数。

(来自@Andrew回答)

h(-NAN) == h(NAN)

Demo