summaryrefslogtreecommitdiffhomepage
path: root/test/hash.cpp
blob: a06d16a6c45e7e586bfe1621b3dfbca89bbdc837 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#include "app.hpp"
#include "compat/int-hash.hpp"
#include "compat/array-size.hpp"
#include "src/global-coords.hpp"
#include "src/world.hpp"
#include <cr/StringView.h>
#include <bitset>
#include <memory>
#include <mg/Functions.h>

namespace floormat {

namespace {

void test_simple()
{
    constexpr StringView list[] = { "foo"_s, "bar"_s, "bar\0"_s, "bar2"_s, "baz"_s, };
    constexpr auto size = array_size(list);

    size_t hashes[size] = {};
    for (auto i = 0uz; i < size; i++)
        hashes[i] = hash_buf(list[i].data(), list[i].size());

    for (auto i = 0uz; i < size; i++)
        for (auto j = i+1; j < size; j++)
        {
            if (hashes[i] == hashes[j])
                fm_abort("hash collision between '%s' and '%s': 0x%p",
                    list[i].data(), list[j].data(), (void*)hashes[i]);
        }
}

template<int16_t CoordMax, int8_t ZMax, uint32_t MaxCollisions>
[[nodiscard]] bool test_collisions(const char* name)
{
    constexpr size_t Iters = (CoordMax*2 + 1)*(CoordMax*2 + 1)*(1+ZMax);
    constexpr size_t BucketCount = Math::max(world::initial_capacity, size_t(Math::ceil(Iters / world::max_load_factor)));
    uint32_t num_collisions = 0, iters = 0;
    auto bitset_ = std::make_unique<std::bitset<BucketCount>>(false);
    auto& bitset = *bitset_;

    for (int16_t j = -CoordMax; j <= CoordMax; j++)
        for (int16_t i = -CoordMax; i <= CoordMax; i++)
            for (int8_t k = 0; k <= ZMax; k++)
            {
                auto p = global_coords{i, j, k};
                auto h = hash_buf(&p, sizeof p);
                h %= BucketCount;
                auto ref = bitset[h];
                if (ref) [[unlikely]]
                    ++num_collisions;
                else
                    ref = true;
                (void)ref;
                iters++;
            }

    fm_assert_equal(Iters, iters);

//#define FM_TEST_DEBUG_PRINT

    if (num_collisions > MaxCollisions)
    {
        DBG_nospace << Debug::color(Debug::Color::Magenta)
                    << "fail" << Debug::resetColor << ": test/hash(\"" << name << "\")"
                    << " collisions " << num_collisions << " > " << MaxCollisions
                    << " (iters:" << iters << " buckets:" << BucketCount << ")";
#ifdef FM_TEST_DEBUG_PRINT
        return false;
#else
        fm_abort("test/hash: too many collisions");
#endif
    }
    else
    {
#ifdef FM_TEST_DEBUG_PRINT
        DBG_nospace << Debug::color(Debug::Color::Cyan) << "pass" << Debug::resetColor
                    << ": test/hash(\"" << name << "\") collisions "
                    << num_collisions << " <= " << MaxCollisions
                    << " (iters:" << iters << " buckets:" << BucketCount << ")";
#endif
        return true;
    }
}

} // namespace

void Test::test_hash()
{
    test_simple();

    bool success = true;

#ifdef FM_TEST_DEBUG_PRINT
    DBG_nospace << "";
#endif

    success &= test_collisions<  3, 0,   0 >("small1");
    success &= test_collisions<  6, 0,   4 >("mid2");
    success &= test_collisions<  7, 0,   5 >("mid2");
    success &= test_collisions<  8, 0,  13 >("mid2");
    success &= test_collisions<  9, 0,  19 >("mid2");
    success &= test_collisions< 10, 0,  26 >("large1");
    success &= test_collisions< 12, 0,  46 >("large2");
    success &= test_collisions< 14, 0,  83 >("large3");
    success &= test_collisions< 15, 0, 109 >("large4");
    success &= test_collisions< 16, 0, 127 >("large5");
    success &= test_collisions<  3, 1,    2 >("zsmall2");
    success &= test_collisions<  4, 1,    6 >("zsmall3");
    success &= test_collisions<  2, 3,    1 >("zmid1");
    success &= test_collisions<  6, 1,   17 >("zmid3");
    success &= test_collisions<  6, 2,   37 >("zmid4");
    success &= test_collisions<  7, 1,   27 >("zmid5");
    success &= test_collisions< 10, 1,  107 >("zlarge2");
    success &= test_collisions< 10, 4,  271 >("zlarge3");
    success &= test_collisions< 16, 8, 1114 >("zlarge4");

    if (!success)
    {
        DBG_nospace << "";
        fm_abort("test/hash: too many collisions");
    }
}

} // namespace floormat