diff --git a/Makefile.am b/Makefile.am index b6e8d2c6..27d9dcda 100644 --- a/Makefile.am +++ b/Makefile.am @@ -263,6 +263,8 @@ UTIL_FILES = \ quaternion.cpp \ quaternion.hpp \ raii.hpp \ + rand/lcg.cpp \ + rand/lcg.hpp \ rand/xorshift.cpp \ rand/xorshift.hpp \ random.cpp \ @@ -425,7 +427,7 @@ TEST_BIN = \ test/point \ test/polynomial \ test/pool \ - test/rand/xorshift \ + test/rand/buckets \ test/random \ test/range \ test/rational \ diff --git a/rand/lcg.cpp b/rand/lcg.cpp new file mode 100644 index 00000000..f62701fb --- /dev/null +++ b/rand/lcg.cpp @@ -0,0 +1,60 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright 2015 Danny Robson + */ + +#include "lcg.hpp" + +using util::rand::lcg; + + +template +static constexpr +bool is_coprime (T M, T C) +{ + if (M == 0) + return true; + if (util::gcd (M, C) == 1u) + return true; + return false; +} + + +/////////////////////////////////////////////////////////////////////////////// +template +lcg::lcg (T seed): + m_x (seed) +{ + // ensure this assertion isn't in a header, it could be pretty expensive + // to evaluate often. + static_assert (is_coprime (M, C), + "multiplier and increment must be coprime"); +} + + +/////////////////////////////////////////////////////////////////////////////// +template +T +lcg::operator() (void) +{ + m_x = (A * m_x + C); + if (M != 0) + m_x %= M; + return m_x; +} + + +/////////////////////////////////////////////////////////////////////////////// +template struct util::rand::lcg; +template struct util::rand::lcg; diff --git a/rand/lcg.hpp b/rand/lcg.hpp new file mode 100644 index 00000000..8f2e74cc --- /dev/null +++ b/rand/lcg.hpp @@ -0,0 +1,47 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright 2015 Danny Robson + */ + +#ifndef __UTIL_RAND_LCG_HPP +#define __UTIL_RAND_LCG_HPP + +#include "../maths.hpp" + + +namespace util { namespace rand { + /// linear congruential generator + /// + /// T: output/state type + /// M: modulus + /// A: multiplier + /// C: increment + template + struct lcg { + public: + static_assert (std::is_unsigned::value, + "LCG generates integer overflow which is undefined behaviour for signed types"); + lcg (T seed); + + T operator() (void); + + private: + T m_x; + }; + + // glibc: typedef lcg lcg_t; + using lcg_t = lcg; +} } + +#endif diff --git a/test/.gitignore b/test/.gitignore index 72584ba7..20b528db 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -15,7 +15,8 @@ /options /point /pool -/rand +/random +/rand/buckets /range /region /ripemd diff --git a/test/rand/buckets.cpp b/test/rand/buckets.cpp new file mode 100644 index 00000000..d5d15efe --- /dev/null +++ b/test/rand/buckets.cpp @@ -0,0 +1,64 @@ +#include "rand/xorshift.hpp" +#include "rand/lcg.hpp" + +#include "tap.hpp" +#include "maths.hpp" +#include "types/string.hpp" + + +/////////////////////////////////////////////////////////////////////////////// +template <> +std::string +type_to_string> (void) { return "xorshift"; } + +template <> +std::string +type_to_string> (void) { return "xorshift"; } + +template <> +std::string +type_to_string (void) { return "lcg_t"; } + + +/////////////////////////////////////////////////////////////////////////////// +/// check random outputs are roughly divisible between a number of fixed width +/// buckets. +/// +/// this is not anything close to a strict statistical test. it is more aimed +/// at link testing and basic functionality verification within a small +/// resource footprint (ie, fast unit testing). +template +void +test_buckets (util::TAP::logger &tap, Args&& ...args) +{ + constexpr unsigned BUCKET_BITS = 8u; + constexpr size_t BUCKET_COUNT = 1u << BUCKET_BITS; + constexpr unsigned BUCKET_MASK = BUCKET_COUNT - 1u; + constexpr unsigned EXPECTED = 1024u; + constexpr unsigned ITERATIONS = BUCKET_COUNT * EXPECTED; + + unsigned buckets[BUCKET_COUNT] = {}; + G gen (std::forward (args)...); + + for (unsigned i = 0; i < ITERATIONS; ++i) + ++buckets[gen () & BUCKET_MASK]; + + tap.expect (std::find_if (std::cbegin (buckets), + std::cend (buckets), + [] (auto v) { return v < EXPECTED * 3 / 4; }) == std::cend (buckets), + "bucket counts for %s", type_to_string ()); +} + + +/////////////////////////////////////////////////////////////////////////////// +int +main (int,char**) +{ + util::TAP::logger tap; + + test_buckets> (tap, 0x1234u); + test_buckets> (tap, 0x1234u); + test_buckets (tap, 0x1234u); + + return tap.status (); +}