Browse Source

random and tests

Kajetan Johannes Hammerle 3 years ago
parent
commit
1fd8e086b0
8 changed files with 172 additions and 1 deletions
  1. 2 0
      Main.cpp
  2. 1 1
      meson.build
  3. 77 0
      tests/RandomTests.cpp
  4. 8 0
      tests/RandomTests.h
  5. 13 0
      tests/Test.cpp
  6. 2 0
      tests/Test.h
  7. 46 0
      utils/Random.cpp
  8. 23 0
      utils/Random.h

+ 2 - 0
Main.cpp

@@ -3,6 +3,7 @@
 #include "tests/ListTests.h"
 #include "tests/BitArrayTests.h"
 #include "tests/StringBufferTests.h"
+#include "tests/RandomTests.h"
 
 int main() {
     ArrayTests::test();
@@ -10,5 +11,6 @@ int main() {
     ListTests::test();
     BitArrayTests::test();
     StringBufferTests::test();
+    RandomTests::test();
     return 0;
 }

+ 1 - 1
meson.build

@@ -1,6 +1,6 @@
 project('gaming core tests', 'cpp')
 
-sources = ['Main.cpp', 'tests/Test.cpp', 'tests/ArrayTests.cpp', 'tests/HashMapTests.cpp', 'tests/ListTests.cpp', 'tests/BitArrayTests.cpp', 'tests/StringBufferTests.cpp']
+sources = ['Main.cpp', 'tests/Test.cpp', 'tests/ArrayTests.cpp', 'tests/HashMapTests.cpp', 'tests/ListTests.cpp', 'tests/BitArrayTests.cpp', 'tests/StringBufferTests.cpp', 'tests/RandomTests.cpp', 'utils/Random.cpp']
 
 executable('tests', 
     sources: sources,

+ 77 - 0
tests/RandomTests.cpp

@@ -0,0 +1,77 @@
+#include "tests/RandomTests.h"
+#include "tests/Test.h"
+#include "utils/Random.h"
+#include "utils/Array.h"
+
+static void testAverage(Test& test) {
+    Random r(553);
+    float sum = 0;
+    for(int i = 0; i < 1000000; i++) {
+        sum += r.next(2, 10);
+    }
+    sum /= 1000000.0f;
+    test.checkFloat(6.0f, sum, 0.01f, "average");
+}
+
+static void testCoin(Test& test) {
+    Random r(553);
+    Array<int, 2> c(0);
+    for(int i = 0; i < 1000000; i++) {
+        c[r.next() & 1]++;
+    }
+    test.checkFloat(0.0f, (c[0] - c[1]) / 1000000.0f, 0.001f, "coin");
+}
+
+static void testDistribution(Test& test) {
+    Random r(553);
+    Array<int, 102> c(0);
+    for(int i = 0; i < 1000000; i++) {
+        c[r.next(1, c.getLength() - 2)]++;
+    }
+    test.checkEqual(0, c[0], "distribution does not underflow");
+    test.checkEqual(0, c[c.getLength() - 1], "distribution does not overflow");
+    for(int i = 1; i < c.getLength() - 1; i++) {
+        test.checkFloat(0.01f, c[i] / 1000000.0f, 0.001f, "distribution");
+    }
+}
+
+static void testFloatAverage(Test& test) {
+    Random r(553);
+    float sum = 0;
+    for(int i = 0; i < 1000000; i++) {
+        sum += r.nextFloat();
+    }
+    sum /= 1000000.0f;
+    test.checkFloat(0.5f, sum, 0.001f, "float average");
+}
+
+static void testFloatCoin(Test& test) {
+    Random r(553);
+    Array<int, 2> c(0);
+    for(int i = 0; i < 1000000; i++) {
+        c[r.nextFloat() < 0.5f]++;
+    }
+    test.checkFloat(0.0f, (c[0] - c[1]) / 1000000.0f, 0.001f, "coin");
+}
+
+static void testFloatDistribution(Test& test) {
+    Random r(553);
+    Array<int, 100> c(0);
+    for(int i = 0; i < 1000000; i++) {
+        c[r.nextFloat() * c.getLength()]++;
+    }
+    for(int i = 0; i < c.getLength(); i++) {
+        test.checkFloat(0.01f, c[i] / 1000000.0f, 0.001f, "distribution");
+    }
+}
+
+void RandomTests::test() {
+    Test test("Random");
+    testAverage(test);
+    testCoin(test);
+    testDistribution(test);
+    testFloatAverage(test);
+    testFloatCoin(test);
+    testFloatDistribution(test);
+    test.finalize();
+}

+ 8 - 0
tests/RandomTests.h

@@ -0,0 +1,8 @@
+#ifndef RANDOMTESTS_H
+#define RANDOMTESTS_H
+
+namespace RandomTests {
+    void test();
+}
+
+#endif

+ 13 - 0
tests/Test.cpp

@@ -8,4 +8,17 @@ void Test::finalize() {
     std::cout << name << " Tests: " << successTests << " / " << tests << " succeeded\n" << RESET;
     tests = 0;
     successTests = 0;
+}
+
+void Test::checkFloat(float wanted, float actual, float error, const char* text) {
+    float diff = wanted - actual;
+    diff = diff < 0.0f ? -diff : diff;
+    if(diff < error) {
+        tests++;
+        successTests++;
+    } else {
+        tests++;
+        std::cout << RED << name << " Test " << tests << ": " << text << " - " << RESET;
+        std::cout << RED << "expected '" << wanted << "' got '" << actual << "'\n" << RESET;
+    }
 }

+ 2 - 0
tests/Test.h

@@ -27,6 +27,8 @@ public:
             std::cout << RED << "expected '" << wanted << "' got '" << actual << "'\n" << RESET;
         }
     }
+    
+    void checkFloat(float wanted, float actual, float error, const char* text);
 };
 
 #endif

+ 46 - 0
utils/Random.cpp

@@ -0,0 +1,46 @@
+#include <chrono>
+#include <iostream>
+
+#include "utils/Random.h"
+
+Random::Random(int seed) : index(0) {
+    for(int i = 0; i < N; i++) {
+        data[i] = seed;
+        seed = seed * 7 + 31;
+    }
+}
+
+Random::Random() : Random(std::chrono::steady_clock::now().time_since_epoch().count()) {
+}
+
+float Random::nextFloat() {
+    static constexpr int bits = sizeof(int) * 6;
+    static constexpr int mask = (1 << bits) - 1;
+    return (next() & mask) * (1.0f / (1.0f + mask));
+}
+
+void Random::update() {
+    static const int map[2] = {0, -1900031960};
+    for(int i = 0; i < N - M; i++) {
+        data[i] = data[i + M] ^ (data[i] >> 1) ^ map[data[i] & 1];
+    }
+    for(int i = N - M; i < N; i++) {
+        data[i] = data[i + (M - N)] ^ (data[i] >> 1) ^ map[data[i] & 1];
+    }
+    index = 0;
+}
+
+int Random::next() {
+    if(index >= N) {
+        update();
+    }
+    int r = data[index++];
+    r ^= (r << 7) & 0x2B5B2500;
+    r ^= (r << 15) & 0xDB8B0000;
+    r ^= (r >> 16);
+    return r & static_cast<int> (-1u >> 1);
+}
+
+int Random::next(int min, int max) {
+    return min + next() % (max - min + 1);
+}

+ 23 - 0
utils/Random.h

@@ -0,0 +1,23 @@
+#ifndef RANDOM_H
+#define RANDOM_H
+
+class Random final {
+public:
+    Random(int seed);
+    Random();
+
+    int next();
+    int next(int min, int max);
+    float nextFloat();
+
+private:
+    constexpr static int N = 25;
+    constexpr static int M = 7;
+    
+    void update();
+
+    int data[N];
+    int index;
+};
+
+#endif