rand: add uniform distribution routines
This commit is contained in:
parent
82d3c36ebd
commit
f116422086
@ -487,6 +487,8 @@ list (
|
|||||||
"${CMAKE_CURRENT_BINARY_DIR}/prefix/${PREFIX}/preprocessor.hpp"
|
"${CMAKE_CURRENT_BINARY_DIR}/prefix/${PREFIX}/preprocessor.hpp"
|
||||||
quaternion.cpp
|
quaternion.cpp
|
||||||
quaternion.hpp
|
quaternion.hpp
|
||||||
|
rand/distribution/uniform.cpp
|
||||||
|
rand/distribution/uniform.hpp
|
||||||
rand/generic.hpp
|
rand/generic.hpp
|
||||||
rand/lcg.cpp
|
rand/lcg.cpp
|
||||||
rand/lcg.hpp
|
rand/lcg.hpp
|
||||||
|
1
rand/distribution/uniform.cpp
Normal file
1
rand/distribution/uniform.cpp
Normal file
@ -0,0 +1 @@
|
|||||||
|
#include "./uniform.hpp"
|
228
rand/distribution/uniform.hpp
Normal file
228
rand/distribution/uniform.hpp
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*
|
||||||
|
* Copyright 2020, Danny Robson <danny@nerdcruft.net>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../../debug/assert.hpp"
|
||||||
|
#include "../../cast.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
namespace cruft::rand::distribution {
|
||||||
|
template <typename ResultT>
|
||||||
|
struct uniform_int_distribution {
|
||||||
|
static_assert (std::is_integral_v<ResultT>);
|
||||||
|
using result_type = ResultT;
|
||||||
|
|
||||||
|
struct param_type {
|
||||||
|
result_type a;
|
||||||
|
result_type b;
|
||||||
|
};
|
||||||
|
|
||||||
|
uniform_int_distribution (
|
||||||
|
result_type _a,
|
||||||
|
result_type _b = std::numeric_limits<ResultT>::max ()
|
||||||
|
)
|
||||||
|
: uniform_int_distribution (param_type { .a = _a, .b = _b })
|
||||||
|
{ ; }
|
||||||
|
|
||||||
|
uniform_int_distribution ()
|
||||||
|
: uniform_int_distribution (0)
|
||||||
|
{ ; }
|
||||||
|
|
||||||
|
uniform_int_distribution (param_type const &_param)
|
||||||
|
: m_param (_param)
|
||||||
|
{ ; }
|
||||||
|
|
||||||
|
void reset (void);
|
||||||
|
|
||||||
|
|
||||||
|
template <typename GeneratorT>
|
||||||
|
result_type operator() (GeneratorT &gen)
|
||||||
|
{
|
||||||
|
return this->template operator() (gen, m_param);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename GeneratorT>
|
||||||
|
result_type
|
||||||
|
operator() (GeneratorT &gen, param_type const &p)
|
||||||
|
{
|
||||||
|
// We use the same approach as libstdc++.
|
||||||
|
//
|
||||||
|
// Specialising for downscaling gen, upscaling gen, and identify
|
||||||
|
// gen transforms.
|
||||||
|
|
||||||
|
using num_t = std::common_type_t<result_type, typename GeneratorT::result_type>;
|
||||||
|
num_t const gen_range = gen.max () - gen.min ();
|
||||||
|
num_t const our_range = p.b - p.a;
|
||||||
|
|
||||||
|
if (gen_range > our_range) {
|
||||||
|
// The output of gen can be seen as: (range * scale) + bias.
|
||||||
|
//
|
||||||
|
// We calculate the scale to fit gen's range...
|
||||||
|
num_t const range = our_range + 1;
|
||||||
|
num_t const scale = gen_range / range;
|
||||||
|
num_t const limit = range * scale;
|
||||||
|
|
||||||
|
// ...and use rejection sampling if the value falls outside
|
||||||
|
// the target range...
|
||||||
|
num_t res;
|
||||||
|
do {
|
||||||
|
res = gen () - gen.min ();
|
||||||
|
} while (res >= limit);
|
||||||
|
|
||||||
|
// ...and rescale back to the target range, and add the
|
||||||
|
// target offset.
|
||||||
|
return cruft::cast::lossless<result_type> (res / scale + p.a);
|
||||||
|
} else if (gen_range < our_range) {
|
||||||
|
// The output range can be modelled as (range * scale) + bias
|
||||||
|
num_t top, low, res;
|
||||||
|
num_t const range = gen_range + 1;
|
||||||
|
num_t const scale = our_range / range;
|
||||||
|
|
||||||
|
CHECK_LE (scale, std::numeric_limits<result_type>::max ());
|
||||||
|
|
||||||
|
param_type p_ {
|
||||||
|
.a = 0,
|
||||||
|
.b = cruft::cast::lossless<result_type> (scale),
|
||||||
|
};
|
||||||
|
|
||||||
|
do {
|
||||||
|
top = range * this->operator() (gen, p_);
|
||||||
|
low = gen () - gen.min ();
|
||||||
|
res = top + low;
|
||||||
|
} while (res > our_range || res < top);
|
||||||
|
|
||||||
|
return cruft::cast::lossless<result_type> (res + p.a);
|
||||||
|
} else {
|
||||||
|
return cruft::cast::lossless<result_type> (gen () - gen.min () + p.a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result_type a (void) const { return m_param.a; }
|
||||||
|
result_type b (void) const { return m_param.b; }
|
||||||
|
|
||||||
|
param_type param (void) const { return m_param; }
|
||||||
|
void param (param_type const &_param) { m_param = _param; }
|
||||||
|
|
||||||
|
result_type min (void) const { return m_param.a; }
|
||||||
|
result_type max (void) const { return m_param.b; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
param_type m_param;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template <typename ResultT>
|
||||||
|
bool operator== (
|
||||||
|
uniform_int_distribution<ResultT> const&,
|
||||||
|
uniform_int_distribution<ResultT> const&
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
template <typename ResultT>
|
||||||
|
bool operator!= (
|
||||||
|
uniform_int_distribution<ResultT> const&,
|
||||||
|
uniform_int_distribution<ResultT> const&
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
template <typename RealT, std::size_t BitsV, typename GeneratorT>
|
||||||
|
RealT
|
||||||
|
generate_canonical (GeneratorT &gen)
|
||||||
|
{
|
||||||
|
static_assert (std::is_floating_point_v<RealT>);
|
||||||
|
|
||||||
|
static constexpr std::size_t b = std::min (
|
||||||
|
BitsV,
|
||||||
|
std::size_t (std::numeric_limits<RealT>::digits)
|
||||||
|
);
|
||||||
|
|
||||||
|
// We use a RealT here so that we can avoid overflow when the
|
||||||
|
// generator output spans the range of size_t (given the +1).
|
||||||
|
RealT R = RealT (gen.max () - gen.min ()) + 1;
|
||||||
|
// Ideally we'd compute this without floating point overhead by using
|
||||||
|
// integral log2 and summation identities.
|
||||||
|
std::size_t const log2R = std::size_t (std::log (R) / std::log (2));
|
||||||
|
std::size_t const k = std::max<std::size_t> (1, (b + log2R - 1) / log2R);
|
||||||
|
|
||||||
|
RealT base = 1;
|
||||||
|
RealT accum = 0;
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < k; ++i) {
|
||||||
|
accum += RealT (gen () - gen.min ()) * base;
|
||||||
|
base *= R;
|
||||||
|
}
|
||||||
|
|
||||||
|
return accum / base;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ResultT>
|
||||||
|
struct uniform_real_distribution {
|
||||||
|
static_assert (std::is_floating_point_v<ResultT>);
|
||||||
|
|
||||||
|
using result_type = ResultT;
|
||||||
|
|
||||||
|
struct param_type {
|
||||||
|
result_type a;
|
||||||
|
result_type b;
|
||||||
|
};
|
||||||
|
|
||||||
|
uniform_real_distribution (
|
||||||
|
result_type _a,
|
||||||
|
result_type _b = 1
|
||||||
|
)
|
||||||
|
: uniform_real_distribution (param_type { .a = _a, .b = _b })
|
||||||
|
{ ; }
|
||||||
|
|
||||||
|
uniform_real_distribution ()
|
||||||
|
: uniform_real_distribution (0)
|
||||||
|
{ ; }
|
||||||
|
|
||||||
|
uniform_real_distribution (param_type const &_param)
|
||||||
|
: m_param (_param)
|
||||||
|
{ ; }
|
||||||
|
|
||||||
|
void reset (void);
|
||||||
|
|
||||||
|
|
||||||
|
template <typename GeneratorT>
|
||||||
|
result_type operator() (GeneratorT &gen)
|
||||||
|
{
|
||||||
|
return this->template operator() (gen, m_param);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename GeneratorT>
|
||||||
|
result_type
|
||||||
|
operator() (GeneratorT &gen, param_type const &p)
|
||||||
|
{
|
||||||
|
return ::cruft::rand::distribution::generate_canonical<
|
||||||
|
result_type,
|
||||||
|
std::numeric_limits<result_type>::digits
|
||||||
|
> (gen) * (p.b - p.a) + p.a;
|
||||||
|
}
|
||||||
|
|
||||||
|
result_type a (void) const { return m_param.a; }
|
||||||
|
result_type b (void) const { return m_param.b; }
|
||||||
|
|
||||||
|
param_type param (void) const { return m_param; }
|
||||||
|
void param (param_type const &_param) { m_param = _param; }
|
||||||
|
|
||||||
|
result_type min (void) const { return m_param.a; }
|
||||||
|
result_type max (void) const { return m_param.b; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
param_type m_param;
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user