1 /* boost random/detail/qrng_base.hpp header file
2 *
3 * Copyright Justinas Vygintas Daugmaudis 2010-2018
4 * Distributed under the Boost Software License, Version 1.0. (See
5 * accompanying file LICENSE_1_0.txt or copy at
6 * http://www.boost.org/LICENSE_1_0.txt)
7 */
8
9 #ifndef BOOST_RANDOM_DETAIL_QRNG_BASE_HPP
10 #define BOOST_RANDOM_DETAIL_QRNG_BASE_HPP
11
12 #include <stdexcept>
13 #include <vector>
14 #include <limits>
15
16 #include <istream>
17 #include <ostream>
18 #include <sstream>
19
20 #include <boost/cstdint.hpp>
21 #include <boost/random/detail/operators.hpp>
22
23 #include <boost/throw_exception.hpp>
24
25 #include <boost/mpl/bool.hpp>
26
27 #include <boost/random/detail/disable_warnings.hpp>
28
29 //!\file
30 //!Describes the quasi-random number generator base class template.
31
32 namespace boost {
33 namespace random {
34
35 namespace qrng_detail {
36
37 // If the seed is a signed integer type, then we need to
38 // check that the value is positive:
39 template <typename Integer>
check_seed_sign(const Integer & v,const mpl::true_)40 inline void check_seed_sign(const Integer& v, const mpl::true_)
41 {
42 if (v < 0)
43 {
44 boost::throw_exception( std::range_error("seed must be a positive integer") );
45 }
46 }
47 template <typename Integer>
check_seed_sign(const Integer &,const mpl::false_)48 inline void check_seed_sign(const Integer&, const mpl::false_) {}
49
50 template <typename Integer>
check_seed_sign(const Integer & v)51 inline void check_seed_sign(const Integer& v)
52 {
53 check_seed_sign(v, mpl::bool_<std::numeric_limits<Integer>::is_signed>());
54 }
55
56
57 template<typename DerivedT, typename LatticeT, typename SizeT>
58 class qrng_base
59 {
60 public:
61 typedef SizeT size_type;
62 typedef typename LatticeT::value_type result_type;
63
qrng_base(std::size_t dimension)64 explicit qrng_base(std::size_t dimension)
65 // Guard against invalid dimensions before creating the lattice
66 : lattice(prevent_zero_dimension(dimension))
67 , quasi_state(dimension)
68 {
69 derived().seed();
70 }
71
72 // default copy c-tor is fine
73
74 // default assignment operator is fine
75
76 //!Returns: The dimension of of the quasi-random domain.
77 //!
78 //!Throws: nothing.
dimension() const79 std::size_t dimension() const { return quasi_state.size(); }
80
81 //!Returns: Returns a successive element of an s-dimensional
82 //!(s = X::dimension()) vector at each invocation. When all elements are
83 //!exhausted, X::operator() begins anew with the starting element of a
84 //!subsequent s-dimensional vector.
85 //!
86 //!Throws: range_error.
operator ()()87 result_type operator()()
88 {
89 return curr_elem != dimension() ? load_cached(): next_state();
90 }
91
92 //!Fills a range with quasi-random values.
generate(Iter first,Iter last)93 template<typename Iter> void generate(Iter first, Iter last)
94 {
95 for (; first != last; ++first)
96 *first = this->operator()();
97 }
98
99 //!Effects: Advances *this state as if z consecutive
100 //!X::operator() invocations were executed.
101 //!
102 //!Throws: range_error.
discard(boost::uintmax_t z)103 void discard(boost::uintmax_t z)
104 {
105 const std::size_t dimension_value = dimension();
106
107 // Compiler knows how to optimize subsequent x / y and x % y
108 // statements. In fact, gcc does this even at -O1, so don't
109 // be tempted to "optimize" % via subtraction and multiplication.
110
111 boost::uintmax_t vec_n = z / dimension_value;
112 std::size_t carry = curr_elem + (z % dimension_value);
113
114 vec_n += carry / dimension_value;
115 carry = carry % dimension_value;
116
117 // Avoid overdiscarding by branchlessly correcting the triple
118 // (D, S + 1, 0) to (D, S, D) (see equality operator)
119 const bool corr = (!carry) & static_cast<bool>(vec_n);
120
121 // Discards vec_n (with correction) consecutive s-dimensional vectors
122 discard_vector(vec_n - static_cast<boost::uintmax_t>(corr));
123
124 #ifdef BOOST_MSVC
125 #pragma warning(push)
126 // disable unary minus operator applied to an unsigned type,
127 // result still unsigned.
128 #pragma warning(disable:4146)
129 #endif
130
131 // Sets up the proper position of the element-to-read
132 // curr_elem = carry + corr*dimension_value
133 curr_elem = carry ^ (-static_cast<std::size_t>(corr) & dimension_value);
134
135 #ifdef BOOST_MSVC
136 #pragma warning(pop)
137 #endif
138 }
139
140 //!Writes the textual representation of the generator to a @c std::ostream.
BOOST_RANDOM_DETAIL_OSTREAM_OPERATOR(os,qrng_base,s)141 BOOST_RANDOM_DETAIL_OSTREAM_OPERATOR(os, qrng_base, s)
142 {
143 os << s.dimension() << " " << s.seq_count << " " << s.curr_elem;
144 return os;
145 }
146
147 //!Reads the textual representation of the generator from a @c std::istream.
BOOST_RANDOM_DETAIL_ISTREAM_OPERATOR(is,qrng_base,s)148 BOOST_RANDOM_DETAIL_ISTREAM_OPERATOR(is, qrng_base, s)
149 {
150 std::size_t dim;
151 size_type seed;
152 boost::uintmax_t z;
153 if (is >> dim >> std::ws >> seed >> std::ws >> z) // initialize iff success!
154 {
155 // Check seed sign before resizing the lattice and/or recomputing state
156 check_seed_sign(seed);
157
158 if (s.dimension() != prevent_zero_dimension(dim))
159 {
160 s.lattice.resize(dim);
161 s.quasi_state.resize(dim);
162 }
163 // Fast-forward to the correct state
164 s.derived().seed(seed);
165 if (z != 0) s.discard(z);
166 }
167 return is;
168 }
169
170 //!Returns true if the two generators will produce identical sequences of outputs.
BOOST_RANDOM_DETAIL_EQUALITY_OPERATOR(qrng_base,x,y)171 BOOST_RANDOM_DETAIL_EQUALITY_OPERATOR(qrng_base, x, y)
172 {
173 const std::size_t dimension_value = x.dimension();
174
175 // Note that two generators with different seq_counts and curr_elems can
176 // produce the same sequence because the generator triple
177 // (D, S, D) is equivalent to (D, S + 1, 0), where D is dimension, S -- seq_count,
178 // and the last one is curr_elem.
179
180 return (dimension_value == y.dimension()) &&
181 // |x.seq_count - y.seq_count| <= 1
182 !((x.seq_count < y.seq_count ? y.seq_count - x.seq_count : x.seq_count - y.seq_count)
183 > static_cast<size_type>(1)) &&
184 // Potential overflows don't matter here, since we've already ascertained
185 // that sequence counts differ by no more than 1, so if they overflow, they
186 // can overflow together.
187 (x.seq_count + (x.curr_elem / dimension_value) == y.seq_count + (y.curr_elem / dimension_value)) &&
188 (x.curr_elem % dimension_value == y.curr_elem % dimension_value);
189 }
190
191 //!Returns true if the two generators will produce different sequences of outputs.
192 BOOST_RANDOM_DETAIL_INEQUALITY_OPERATOR(qrng_base)
193
194 protected:
195 typedef std::vector<result_type> state_type;
196 typedef typename state_type::iterator state_iterator;
197
198 // Getters
curr_seq() const199 size_type curr_seq() const { return seq_count; }
200
state_begin()201 state_iterator state_begin() { return quasi_state.begin(); }
state_end()202 state_iterator state_end() { return quasi_state.end(); }
203
204 // Setters
reset_seq(size_type seq)205 void reset_seq(size_type seq)
206 {
207 seq_count = seq;
208 curr_elem = 0u;
209 }
210
211 private:
derived()212 DerivedT& derived() throw()
213 {
214 return *static_cast<DerivedT * const>(this);
215 }
216
217 // Load the result from the saved state.
load_cached()218 result_type load_cached()
219 {
220 return quasi_state[curr_elem++];
221 }
222
next_state()223 result_type next_state()
224 {
225 size_type new_seq = seq_count;
226 if (BOOST_LIKELY(++new_seq > seq_count))
227 {
228 derived().compute_seq(new_seq);
229 reset_seq(new_seq);
230 return load_cached();
231 }
232 boost::throw_exception( std::range_error("qrng_base: next_state") );
233 }
234
235 // Discards z consecutive s-dimensional vectors,
236 // and preserves the position of the element-to-read
discard_vector(boost::uintmax_t z)237 void discard_vector(boost::uintmax_t z)
238 {
239 const boost::uintmax_t max_z = std::numeric_limits<size_type>::max() - seq_count;
240
241 // Don't allow seq_count + z overflows here
242 if (max_z < z)
243 boost::throw_exception( std::range_error("qrng_base: discard_vector") );
244
245 std::size_t tmp = curr_elem;
246 derived().seed(static_cast<size_type>(seq_count + z));
247 curr_elem = tmp;
248 }
249
prevent_zero_dimension(std::size_t dimension)250 static std::size_t prevent_zero_dimension(std::size_t dimension)
251 {
252 if (dimension == 0)
253 boost::throw_exception( std::invalid_argument("qrng_base: zero dimension") );
254 return dimension;
255 }
256
257 // Member variables are so ordered with the intention
258 // that the typical memory access pattern would be
259 // incremental. Moreover, lattice is put before quasi_state
260 // because we want to construct lattice first. Lattices
261 // can do some kind of dimension sanity check (as in
262 // dimension_assert below), and if that fails then we don't
263 // need to do any more work.
264 private:
265 std::size_t curr_elem;
266 size_type seq_count;
267 protected:
268 LatticeT lattice;
269 private:
270 state_type quasi_state;
271 };
272
dimension_assert(const char * generator,std::size_t dim,std::size_t maxdim)273 inline void dimension_assert(const char* generator, std::size_t dim, std::size_t maxdim)
274 {
275 if (!dim || dim > maxdim)
276 {
277 std::ostringstream os;
278 os << "The " << generator << " quasi-random number generator only supports dimensions in range [1; "
279 << maxdim << "], but dimension " << dim << " was supplied.";
280 boost::throw_exception( std::invalid_argument(os.str()) );
281 }
282 }
283
284 } // namespace qrng_detail
285
286 } // namespace random
287 } // namespace boost
288
289 #include <boost/random/detail/enable_warnings.hpp>
290
291 #endif // BOOST_RANDOM_DETAIL_QRNG_BASE_HPP
292