feat: nisps build + host test harness
CMakeLists.txt: - Native host build by default; Emscripten-target detection plumbed but WASM emit deferred to stream 7 (playground build script). - Header-only INTERFACE library `nisps_core`. - Host test executable `nisps_core_tests` compiled with -Wall -Wextra -Werror -Wpedantic (Chris's rules: clean build is non-negotiable). tests/cpp/test_helpers.hpp: - Minimal NISPS_TEST / NISPS_EXPECT / NISPS_EXPECT_NEAR macros, no external deps. Rationale documented in-file: Catch2/doctest would add ~10MB and 30s for what is currently <100 LOC of test runtime. 22 unit tests covering FixedBuffer (5), RingBuffer (5), Rng (7), math (5). All green; verified via `cmake --build nisps/build && ./nisps/build/nisps_core_tests`.
This commit is contained in:
parent
4f60fc8405
commit
e5bf2aa055
7 changed files with 427 additions and 0 deletions
68
nisps/CMakeLists.txt
Normal file
68
nisps/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
cmake_minimum_required(VERSION 3.20)
|
||||||
|
project(nisps_core CXX)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Toolchain / standard
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
# Default to a Release build when invoked without -DCMAKE_BUILD_TYPE so host
|
||||||
|
# tests get optimized math; flip with `-DCMAKE_BUILD_TYPE=Debug` for stepping.
|
||||||
|
if(NOT CMAKE_BUILD_TYPE)
|
||||||
|
set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Core interface library — header-only. Other parts of the build (ml/, dsp/,
|
||||||
|
# engines/, modes/) will link against this once they exist.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
add_library(nisps_core INTERFACE)
|
||||||
|
target_include_directories(nisps_core
|
||||||
|
INTERFACE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
target_compile_features(nisps_core INTERFACE cxx_std_20)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Emscripten / WASM target detection
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# When building for WASM via emcmake, ${EMSCRIPTEN} is set automatically. We
|
||||||
|
# don't actually emit a WASM binary from this CMakeLists yet — the WASM build
|
||||||
|
# script lives in stream 7 (playground/build) and assembles its own
|
||||||
|
# Emscripten link command. Here we just gate the host-only test executable so
|
||||||
|
# `emcmake cmake -S nisps -B build-wasm` configures cleanly.
|
||||||
|
if(EMSCRIPTEN)
|
||||||
|
message(STATUS "nisps_core: configuring for Emscripten/WASM target")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Test scaffold — only built on the host. We use a hand-rolled assertion-
|
||||||
|
# based harness (see tests/cpp/test_helpers.hpp) rather than Catch2/doctest;
|
||||||
|
# the rationale is in test_helpers.hpp.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if(NOT EMSCRIPTEN)
|
||||||
|
set(NISPS_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../tests/cpp)
|
||||||
|
|
||||||
|
add_executable(nisps_core_tests
|
||||||
|
${NISPS_TEST_DIR}/test_main.cpp
|
||||||
|
${NISPS_TEST_DIR}/test_fixed_buffer.cpp
|
||||||
|
${NISPS_TEST_DIR}/test_ring_buffer.cpp
|
||||||
|
${NISPS_TEST_DIR}/test_rng.cpp
|
||||||
|
${NISPS_TEST_DIR}/test_math.cpp
|
||||||
|
)
|
||||||
|
target_link_libraries(nisps_core_tests PRIVATE nisps_core)
|
||||||
|
|
||||||
|
# Chris's rule: the core compiles cleanly under -Wall -Wextra -Werror.
|
||||||
|
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
|
||||||
|
target_compile_options(nisps_core_tests PRIVATE
|
||||||
|
-Wall -Wextra -Werror -Wpedantic
|
||||||
|
)
|
||||||
|
elseif(MSVC)
|
||||||
|
target_compile_options(nisps_core_tests PRIVATE /W4 /WX)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
add_test(NAME nisps_core_tests COMMAND nisps_core_tests)
|
||||||
|
endif()
|
||||||
52
tests/cpp/test_fixed_buffer.cpp
Normal file
52
tests/cpp/test_fixed_buffer.cpp
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
// tests/cpp/test_fixed_buffer.cpp — exercises FixedBuffer's cursor semantics
|
||||||
|
// and capacity guard.
|
||||||
|
|
||||||
|
#include "test_helpers.hpp"
|
||||||
|
#include "../../nisps/core/fixed_buffer.hpp"
|
||||||
|
|
||||||
|
NISPS_TEST(fixed_buffer_starts_empty) {
|
||||||
|
nisps::FixedBuffer<int, 8> b;
|
||||||
|
NISPS_EXPECT(b.size() == 0u);
|
||||||
|
NISPS_EXPECT(b.empty());
|
||||||
|
NISPS_EXPECT(!b.full());
|
||||||
|
NISPS_EXPECT(b.capacity() == 8u);
|
||||||
|
}
|
||||||
|
|
||||||
|
NISPS_TEST(fixed_buffer_push_and_index) {
|
||||||
|
nisps::FixedBuffer<int, 4> b;
|
||||||
|
NISPS_EXPECT(b.push_back(10));
|
||||||
|
NISPS_EXPECT(b.push_back(20));
|
||||||
|
NISPS_EXPECT(b.push_back(30));
|
||||||
|
NISPS_EXPECT(b.size() == 3u);
|
||||||
|
NISPS_EXPECT(b[0] == 10);
|
||||||
|
NISPS_EXPECT(b[1] == 20);
|
||||||
|
NISPS_EXPECT(b[2] == 30);
|
||||||
|
NISPS_EXPECT(b.front() == 10);
|
||||||
|
NISPS_EXPECT(b.back() == 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
NISPS_TEST(fixed_buffer_refuses_when_full) {
|
||||||
|
nisps::FixedBuffer<int, 2> b;
|
||||||
|
NISPS_EXPECT(b.push_back(1));
|
||||||
|
NISPS_EXPECT(b.push_back(2));
|
||||||
|
NISPS_EXPECT(b.full());
|
||||||
|
NISPS_EXPECT(!b.push_back(3)); // refused
|
||||||
|
NISPS_EXPECT(b.size() == 2u); // unchanged
|
||||||
|
}
|
||||||
|
|
||||||
|
NISPS_TEST(fixed_buffer_clear_resets) {
|
||||||
|
nisps::FixedBuffer<int, 4> b;
|
||||||
|
b.push_back(1); b.push_back(2);
|
||||||
|
b.clear();
|
||||||
|
NISPS_EXPECT(b.empty());
|
||||||
|
NISPS_EXPECT(b.push_back(99));
|
||||||
|
NISPS_EXPECT(b[0] == 99);
|
||||||
|
}
|
||||||
|
|
||||||
|
NISPS_TEST(fixed_buffer_iteration) {
|
||||||
|
nisps::FixedBuffer<int, 8> b;
|
||||||
|
for (int i = 0; i < 5; ++i) b.push_back(i * 2);
|
||||||
|
int sum = 0;
|
||||||
|
for (int v : b) sum += v;
|
||||||
|
NISPS_EXPECT(sum == 0 + 2 + 4 + 6 + 8);
|
||||||
|
}
|
||||||
100
tests/cpp/test_helpers.hpp
Normal file
100
tests/cpp/test_helpers.hpp
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
// tests/cpp/test_helpers.hpp — minimal assertion-based test harness.
|
||||||
|
//
|
||||||
|
// Why roll our own: zero external dependencies, builds in <1s, integrates
|
||||||
|
// trivially with CMake. Catch2/doctest would add a fetch step (≈ 10 MB and
|
||||||
|
// 30s of compile time) for what is currently a handful of asserts. If the
|
||||||
|
// suite grows beyond a few hundred lines we can revisit.
|
||||||
|
//
|
||||||
|
// API:
|
||||||
|
// NISPS_TEST(name) — declare a test
|
||||||
|
// NISPS_EXPECT(cond) — non-fatal check (records, keeps running)
|
||||||
|
// NISPS_ASSERT(cond) — fatal check (aborts the test)
|
||||||
|
// NISPS_EXPECT_NEAR(a, b, eps)
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// NISPS_TEST(my_test) {
|
||||||
|
// NISPS_EXPECT(1 + 1 == 2);
|
||||||
|
// }
|
||||||
|
// int main() { return nisps::test::run_all(); }
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace nisps::test {
|
||||||
|
|
||||||
|
struct TestFailure { const char* msg; };
|
||||||
|
|
||||||
|
struct TestCase {
|
||||||
|
const char* name;
|
||||||
|
void (*fn)();
|
||||||
|
};
|
||||||
|
|
||||||
|
inline std::vector<TestCase>& registry() {
|
||||||
|
static std::vector<TestCase> r;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Registrar {
|
||||||
|
Registrar(const char* name, void (*fn)()) {
|
||||||
|
registry().push_back({name, fn});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline int run_all() {
|
||||||
|
int passed = 0, failed = 0;
|
||||||
|
for (const auto& tc : registry()) {
|
||||||
|
std::printf("[ RUN ] %s\n", tc.name);
|
||||||
|
try {
|
||||||
|
tc.fn();
|
||||||
|
std::printf("[ OK ] %s\n", tc.name);
|
||||||
|
++passed;
|
||||||
|
} catch (const TestFailure& f) {
|
||||||
|
std::printf("[ FAILED ] %s — %s\n", tc.name, f.msg);
|
||||||
|
++failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::printf("\n[==========] %d passed, %d failed\n", passed, failed);
|
||||||
|
return failed == 0 ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace nisps::test
|
||||||
|
|
||||||
|
#define NISPS_TEST_CONCAT_(a, b) a##b
|
||||||
|
#define NISPS_TEST_CONCAT(a, b) NISPS_TEST_CONCAT_(a, b)
|
||||||
|
|
||||||
|
#define NISPS_TEST(name) \
|
||||||
|
static void NISPS_TEST_CONCAT(nisps_test_, name)(); \
|
||||||
|
static ::nisps::test::Registrar NISPS_TEST_CONCAT(nisps_test_reg_, name)( \
|
||||||
|
#name, &NISPS_TEST_CONCAT(nisps_test_, name)); \
|
||||||
|
static void NISPS_TEST_CONCAT(nisps_test_, name)()
|
||||||
|
|
||||||
|
#define NISPS_EXPECT(cond) \
|
||||||
|
do { \
|
||||||
|
if (!(cond)) { \
|
||||||
|
std::fprintf(stderr, \
|
||||||
|
" EXPECT failed at %s:%d: %s\n", \
|
||||||
|
__FILE__, __LINE__, #cond); \
|
||||||
|
throw ::nisps::test::TestFailure{#cond}; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define NISPS_ASSERT(cond) NISPS_EXPECT(cond)
|
||||||
|
|
||||||
|
#define NISPS_EXPECT_NEAR(a, b, eps) \
|
||||||
|
do { \
|
||||||
|
const double da = static_cast<double>(a); \
|
||||||
|
const double db = static_cast<double>(b); \
|
||||||
|
if (std::fabs(da - db) > static_cast<double>(eps)) { \
|
||||||
|
std::fprintf(stderr, \
|
||||||
|
" EXPECT_NEAR failed at %s:%d: %s ≈ %s " \
|
||||||
|
"(|%g - %g| = %g > %g)\n", \
|
||||||
|
__FILE__, __LINE__, #a, #b, da, db, \
|
||||||
|
std::fabs(da - db), static_cast<double>(eps)); \
|
||||||
|
throw ::nisps::test::TestFailure{#a " ≈ " #b}; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
8
tests/cpp/test_main.cpp
Normal file
8
tests/cpp/test_main.cpp
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
// tests/cpp/test_main.cpp — entry point that runs every test registered
|
||||||
|
// across the translation units linked into nisps_core_tests.
|
||||||
|
|
||||||
|
#include "test_helpers.hpp"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
return nisps::test::run_all();
|
||||||
|
}
|
||||||
57
tests/cpp/test_math.cpp
Normal file
57
tests/cpp/test_math.cpp
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
// tests/cpp/test_math.cpp — sanity check on clamping, sigmoid bounds, and
|
||||||
|
// curve catalog endpoint pinning.
|
||||||
|
|
||||||
|
#include "test_helpers.hpp"
|
||||||
|
#include "../../nisps/core/math.hpp"
|
||||||
|
|
||||||
|
NISPS_TEST(clamp01_bounds) {
|
||||||
|
NISPS_EXPECT(nisps::clamp01(-1.f) == 0.f);
|
||||||
|
NISPS_EXPECT(nisps::clamp01( 0.f) == 0.f);
|
||||||
|
NISPS_EXPECT(nisps::clamp01( 0.5f) == 0.5f);
|
||||||
|
NISPS_EXPECT(nisps::clamp01( 1.f) == 1.f);
|
||||||
|
NISPS_EXPECT(nisps::clamp01( 2.f) == 1.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
NISPS_TEST(fast_sigmoid_in_unit_range) {
|
||||||
|
for (float x = -10.f; x <= 10.f; x += 0.5f) {
|
||||||
|
const float y = nisps::fast_sigmoid(x);
|
||||||
|
NISPS_EXPECT(y >= 0.f);
|
||||||
|
NISPS_EXPECT(y <= 1.f);
|
||||||
|
}
|
||||||
|
// Center pinned.
|
||||||
|
NISPS_EXPECT_NEAR(nisps::fast_sigmoid(0.f), 0.5f, 1e-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
NISPS_TEST(fast_sigmoid_matches_exact_within_tolerance) {
|
||||||
|
// Document that fast_sigmoid is within ~2% of exact_sigmoid on [-6, 6].
|
||||||
|
for (float x = -6.f; x <= 6.f; x += 0.25f) {
|
||||||
|
const float fy = nisps::fast_sigmoid(x);
|
||||||
|
const float ey = nisps::exact_sigmoid(x);
|
||||||
|
NISPS_EXPECT_NEAR(fy, ey, 0.02);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NISPS_TEST(curve_endpoints_pinned) {
|
||||||
|
// Every curve must map 0→0 and 1→1 exactly.
|
||||||
|
using nisps::Curve;
|
||||||
|
Curve curves[] = {Curve::linear, Curve::exp, Curve::log, Curve::square,
|
||||||
|
Curve::sqrt, Curve::sigmoid, Curve::cubic};
|
||||||
|
for (Curve c : curves) {
|
||||||
|
NISPS_EXPECT_NEAR(nisps::apply_curve(c, 0.f), 0.0, 1e-5);
|
||||||
|
NISPS_EXPECT_NEAR(nisps::apply_curve(c, 1.f), 1.0, 1e-5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NISPS_TEST(curve_monotone_increasing) {
|
||||||
|
using nisps::Curve;
|
||||||
|
Curve curves[] = {Curve::linear, Curve::exp, Curve::log, Curve::square,
|
||||||
|
Curve::sqrt, Curve::sigmoid, Curve::cubic};
|
||||||
|
for (Curve c : curves) {
|
||||||
|
float prev = nisps::apply_curve(c, 0.f);
|
||||||
|
for (float x = 0.05f; x <= 1.f + 1e-6f; x += 0.05f) {
|
||||||
|
const float y = nisps::apply_curve(c, x);
|
||||||
|
NISPS_EXPECT(y >= prev - 1e-6f); // non-decreasing
|
||||||
|
prev = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
64
tests/cpp/test_ring_buffer.cpp
Normal file
64
tests/cpp/test_ring_buffer.cpp
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
// tests/cpp/test_ring_buffer.cpp — single-threaded SPSC behavior. We don't
|
||||||
|
// stress the memory ordering in unit tests (that would need a multi-thread
|
||||||
|
// fuzz harness); we just confirm the FIFO invariants and the capacity guard.
|
||||||
|
|
||||||
|
#include "test_helpers.hpp"
|
||||||
|
#include "../../nisps/core/ring_buffer.hpp"
|
||||||
|
|
||||||
|
NISPS_TEST(ring_buffer_empty_pop_fails) {
|
||||||
|
nisps::RingBuffer<int, 4> r;
|
||||||
|
int v = -1;
|
||||||
|
NISPS_EXPECT(!r.try_pop(v));
|
||||||
|
NISPS_EXPECT(r.empty_approx());
|
||||||
|
}
|
||||||
|
|
||||||
|
NISPS_TEST(ring_buffer_fifo_order) {
|
||||||
|
nisps::RingBuffer<int, 4> r;
|
||||||
|
NISPS_EXPECT(r.try_push(1));
|
||||||
|
NISPS_EXPECT(r.try_push(2));
|
||||||
|
NISPS_EXPECT(r.try_push(3));
|
||||||
|
int v = 0;
|
||||||
|
NISPS_EXPECT(r.try_pop(v)); NISPS_EXPECT(v == 1);
|
||||||
|
NISPS_EXPECT(r.try_pop(v)); NISPS_EXPECT(v == 2);
|
||||||
|
NISPS_EXPECT(r.try_pop(v)); NISPS_EXPECT(v == 3);
|
||||||
|
NISPS_EXPECT(!r.try_pop(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
NISPS_TEST(ring_buffer_full_push_fails) {
|
||||||
|
nisps::RingBuffer<int, 4> r;
|
||||||
|
NISPS_EXPECT(r.try_push(1));
|
||||||
|
NISPS_EXPECT(r.try_push(2));
|
||||||
|
NISPS_EXPECT(r.try_push(3));
|
||||||
|
NISPS_EXPECT(r.try_push(4));
|
||||||
|
NISPS_EXPECT(!r.try_push(5)); // full
|
||||||
|
NISPS_EXPECT(r.size_approx() == 4u);
|
||||||
|
}
|
||||||
|
|
||||||
|
NISPS_TEST(ring_buffer_wraparound) {
|
||||||
|
nisps::RingBuffer<int, 4> r;
|
||||||
|
int v = 0;
|
||||||
|
// Fill, drain, fill again — exercises index wrap.
|
||||||
|
for (int i = 0; i < 100; ++i) {
|
||||||
|
NISPS_EXPECT(r.try_push(i));
|
||||||
|
NISPS_EXPECT(r.try_pop(v));
|
||||||
|
NISPS_EXPECT(v == i);
|
||||||
|
}
|
||||||
|
NISPS_EXPECT(r.empty_approx());
|
||||||
|
}
|
||||||
|
|
||||||
|
NISPS_TEST(ring_buffer_partial_fill_drain) {
|
||||||
|
nisps::RingBuffer<int, 8> r;
|
||||||
|
for (int i = 0; i < 6; ++i) NISPS_EXPECT(r.try_push(i * 10));
|
||||||
|
NISPS_EXPECT(r.size_approx() == 6u);
|
||||||
|
int v = 0;
|
||||||
|
NISPS_EXPECT(r.try_pop(v)); NISPS_EXPECT(v == 0);
|
||||||
|
NISPS_EXPECT(r.try_pop(v)); NISPS_EXPECT(v == 10);
|
||||||
|
NISPS_EXPECT(r.try_push(60));
|
||||||
|
NISPS_EXPECT(r.try_push(70));
|
||||||
|
NISPS_EXPECT(r.size_approx() == 6u);
|
||||||
|
int expect[] = {20, 30, 40, 50, 60, 70};
|
||||||
|
for (int e : expect) {
|
||||||
|
NISPS_EXPECT(r.try_pop(v));
|
||||||
|
NISPS_EXPECT(v == e);
|
||||||
|
}
|
||||||
|
}
|
||||||
78
tests/cpp/test_rng.cpp
Normal file
78
tests/cpp/test_rng.cpp
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
// tests/cpp/test_rng.cpp — verify deterministic seeding, range bounds, and
|
||||||
|
// reasonable statistical shape on the gaussian helper.
|
||||||
|
|
||||||
|
#include "test_helpers.hpp"
|
||||||
|
#include "../../nisps/core/rng.hpp"
|
||||||
|
|
||||||
|
NISPS_TEST(rng_deterministic_for_same_seed) {
|
||||||
|
nisps::Rng a(42ull);
|
||||||
|
nisps::Rng b(42ull);
|
||||||
|
for (int i = 0; i < 1000; ++i) {
|
||||||
|
NISPS_EXPECT(a.next_u64() == b.next_u64());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NISPS_TEST(rng_diverges_for_different_seeds) {
|
||||||
|
nisps::Rng a(0ull);
|
||||||
|
nisps::Rng b(1ull);
|
||||||
|
int distinct = 0;
|
||||||
|
for (int i = 0; i < 100; ++i) {
|
||||||
|
if (a.next_u64() != b.next_u64()) ++distinct;
|
||||||
|
}
|
||||||
|
// Should differ in nearly every draw (probability of collision ~ 2^-64).
|
||||||
|
NISPS_EXPECT(distinct >= 99);
|
||||||
|
}
|
||||||
|
|
||||||
|
NISPS_TEST(rng_zero_seed_does_not_lock_up) {
|
||||||
|
nisps::Rng r(0ull);
|
||||||
|
// Pure xoshiro with all-zero state is degenerate; our splitmix64 fan-out
|
||||||
|
// should prevent that. Confirm we get nonzero output.
|
||||||
|
bool any_nonzero = false;
|
||||||
|
for (int i = 0; i < 16; ++i) {
|
||||||
|
if (r.next_u64() != 0ull) { any_nonzero = true; break; }
|
||||||
|
}
|
||||||
|
NISPS_EXPECT(any_nonzero);
|
||||||
|
}
|
||||||
|
|
||||||
|
NISPS_TEST(rng_uniform_in_unit_interval) {
|
||||||
|
nisps::Rng r(7ull);
|
||||||
|
for (int i = 0; i < 10000; ++i) {
|
||||||
|
const float v = r.next_float_uniform();
|
||||||
|
NISPS_EXPECT(v >= 0.f);
|
||||||
|
NISPS_EXPECT(v < 1.f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NISPS_TEST(rng_signed_in_minus_plus) {
|
||||||
|
nisps::Rng r(7ull);
|
||||||
|
for (int i = 0; i < 10000; ++i) {
|
||||||
|
const float v = r.next_float_signed();
|
||||||
|
NISPS_EXPECT(v >= -1.f);
|
||||||
|
NISPS_EXPECT(v < 1.f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NISPS_TEST(rng_uniform_mean_near_half) {
|
||||||
|
nisps::Rng r(123ull);
|
||||||
|
double sum = 0.0;
|
||||||
|
constexpr int N = 100000;
|
||||||
|
for (int i = 0; i < N; ++i) sum += r.next_float_uniform();
|
||||||
|
const double mean = sum / N;
|
||||||
|
NISPS_EXPECT_NEAR(mean, 0.5, 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
NISPS_TEST(rng_gaussian_stddev_close_to_one) {
|
||||||
|
nisps::Rng r(99ull);
|
||||||
|
constexpr int N = 100000;
|
||||||
|
double sum = 0.0, sq = 0.0;
|
||||||
|
for (int i = 0; i < N; ++i) {
|
||||||
|
const double v = r.next_float_gaussian(1.f);
|
||||||
|
sum += v;
|
||||||
|
sq += v * v;
|
||||||
|
}
|
||||||
|
const double mean = sum / N;
|
||||||
|
const double var = sq / N - mean * mean;
|
||||||
|
NISPS_EXPECT_NEAR(mean, 0.0, 0.05);
|
||||||
|
// sum-of-three-uniforms gives variance exactly 1.0 in the limit.
|
||||||
|
NISPS_EXPECT_NEAR(var, 1.0, 0.05);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue