pcg_engine.h 10.3 KB
Newer Older
Valentin Platzgummer's avatar
Valentin Platzgummer committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
// Copyright 2018 The Abseil Authors.
//
// 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
//
//      https://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.

#ifndef ABSL_RANDOM_INTERNAL_PCG_ENGINE_H_
#define ABSL_RANDOM_INTERNAL_PCG_ENGINE_H_

#include <type_traits>

#include "absl/base/config.h"
#include "absl/meta/type_traits.h"
#include "absl/numeric/int128.h"
#include "absl/random/internal/fastmath.h"
#include "absl/random/internal/iostream_state_saver.h"

namespace absl {
ABSL_NAMESPACE_BEGIN
namespace random_internal {

// pcg_engine is a simplified implementation of Melissa O'Neil's PCG engine in
// C++.  PCG combines a linear congruential generator (LCG) with output state
// mixing functions to generate each random variate.  pcg_engine supports only a
// single sequence (oneseq), and does not support streams.
//
// pcg_engine is parameterized by two types:
//   Params, which provides the multiplier and increment values;
//   Mix, which mixes the state into the result.
//
template <typename Params, typename Mix>
class pcg_engine {
  static_assert(std::is_same<typename Params::state_type,
                             typename Mix::state_type>::value,
                "Class-template absl::pcg_engine must be parameterized by "
                "Params and Mix with identical state_type");

  static_assert(std::is_unsigned<typename Mix::result_type>::value,
                "Class-template absl::pcg_engine must be parameterized by "
                "an unsigned Mix::result_type");

  using params_type = Params;
  using mix_type = Mix;
  using state_type = typename Mix::state_type;

 public:
  // C++11 URBG interface:
  using result_type = typename Mix::result_type;

  static constexpr result_type(min)() {
    return (std::numeric_limits<result_type>::min)();
  }

  static constexpr result_type(max)() {
    return (std::numeric_limits<result_type>::max)();
  }

  explicit pcg_engine(uint64_t seed_value = 0) { seed(seed_value); }

  template <class SeedSequence,
            typename = typename absl::enable_if_t<
                !std::is_same<SeedSequence, pcg_engine>::value>>
  explicit pcg_engine(SeedSequence&& seq) {
    seed(seq);
  }

  pcg_engine(const pcg_engine&) = default;
  pcg_engine& operator=(const pcg_engine&) = default;
  pcg_engine(pcg_engine&&) = default;
  pcg_engine& operator=(pcg_engine&&) = default;

  result_type operator()() {
    // Advance the LCG state, always using the new value to generate the output.
    state_ = lcg(state_);
    return Mix{}(state_);
  }

  void seed(uint64_t seed_value = 0) {
    state_type tmp = seed_value;
    state_ = lcg(tmp + Params::increment());
  }

  template <class SeedSequence>
  typename absl::enable_if_t<
      !std::is_convertible<SeedSequence, uint64_t>::value, void>
  seed(SeedSequence&& seq) {
    reseed(seq);
  }

  void discard(uint64_t count) { state_ = advance(state_, count); }

  bool operator==(const pcg_engine& other) const {
    return state_ == other.state_;
  }

  bool operator!=(const pcg_engine& other) const { return !(*this == other); }

  template <class CharT, class Traits>
  friend typename absl::enable_if_t<(sizeof(state_type) == 16),
                                    std::basic_ostream<CharT, Traits>&>
  operator<<(
      std::basic_ostream<CharT, Traits>& os,  // NOLINT(runtime/references)
      const pcg_engine& engine) {
    auto saver = random_internal::make_ostream_state_saver(os);
    random_internal::stream_u128_helper<state_type> helper;
    helper.write(pcg_engine::params_type::multiplier(), os);
    os << os.fill();
    helper.write(pcg_engine::params_type::increment(), os);
    os << os.fill();
    helper.write(engine.state_, os);
    return os;
  }

  template <class CharT, class Traits>
  friend typename absl::enable_if_t<(sizeof(state_type) <= 8),
                                    std::basic_ostream<CharT, Traits>&>
  operator<<(
      std::basic_ostream<CharT, Traits>& os,  // NOLINT(runtime/references)
      const pcg_engine& engine) {
    auto saver = random_internal::make_ostream_state_saver(os);
    os << pcg_engine::params_type::multiplier() << os.fill();
    os << pcg_engine::params_type::increment() << os.fill();
    os << engine.state_;
    return os;
  }

  template <class CharT, class Traits>
  friend typename absl::enable_if_t<(sizeof(state_type) == 16),
                                    std::basic_istream<CharT, Traits>&>
  operator>>(
      std::basic_istream<CharT, Traits>& is,  // NOLINT(runtime/references)
      pcg_engine& engine) {                   // NOLINT(runtime/references)
    random_internal::stream_u128_helper<state_type> helper;
    auto mult = helper.read(is);
    auto inc = helper.read(is);
    auto tmp = helper.read(is);
    if (mult != pcg_engine::params_type::multiplier() ||
        inc != pcg_engine::params_type::increment()) {
      // signal failure by setting the failbit.
      is.setstate(is.rdstate() | std::ios_base::failbit);
    }
    if (!is.fail()) {
      engine.state_ = tmp;
    }
    return is;
  }

  template <class CharT, class Traits>
  friend typename absl::enable_if_t<(sizeof(state_type) <= 8),
                                    std::basic_istream<CharT, Traits>&>
  operator>>(
      std::basic_istream<CharT, Traits>& is,  // NOLINT(runtime/references)
      pcg_engine& engine) {                   // NOLINT(runtime/references)
    state_type mult{}, inc{}, tmp{};
    is >> mult >> inc >> tmp;
    if (mult != pcg_engine::params_type::multiplier() ||
        inc != pcg_engine::params_type::increment()) {
      // signal failure by setting the failbit.
      is.setstate(is.rdstate() | std::ios_base::failbit);
    }
    if (!is.fail()) {
      engine.state_ = tmp;
    }
    return is;
  }

 private:
  state_type state_;

  // Returns the linear-congruential generator next state.
  static inline constexpr state_type lcg(state_type s) {
    return s * Params::multiplier() + Params::increment();
  }

  // Returns the linear-congruential arbitrary seek state.
  inline state_type advance(state_type s, uint64_t n) const {
    state_type mult = Params::multiplier();
    state_type inc = Params::increment();
    state_type m = 1;
    state_type i = 0;
    while (n > 0) {
      if (n & 1) {
        m *= mult;
        i = i * mult + inc;
      }
      inc = (mult + 1) * inc;
      mult *= mult;
      n >>= 1;
    }
    return m * s + i;
  }

  template <class SeedSequence>
  void reseed(SeedSequence& seq) {
    using sequence_result_type = typename SeedSequence::result_type;
    constexpr size_t kBufferSize =
        sizeof(state_type) / sizeof(sequence_result_type);
    sequence_result_type buffer[kBufferSize];
    seq.generate(std::begin(buffer), std::end(buffer));
    // Convert the seed output to a single state value.
    state_type tmp = buffer[0];
    for (size_t i = 1; i < kBufferSize; i++) {
      tmp <<= (sizeof(sequence_result_type) * 8);
      tmp |= buffer[i];
    }
    state_ = lcg(tmp + params_type::increment());
  }
};

// Parameterized implementation of the PCG 128-bit oneseq state.
// This provides state_type, multiplier, and increment for pcg_engine.
template <uint64_t kMultA, uint64_t kMultB, uint64_t kIncA, uint64_t kIncB>
class pcg128_params {
 public:
#if ABSL_HAVE_INTRINSIC_INT128
  using state_type = __uint128_t;
  static inline constexpr state_type make_u128(uint64_t a, uint64_t b) {
    return (static_cast<__uint128_t>(a) << 64) | b;
  }
#else
  using state_type = absl::uint128;
  static inline constexpr state_type make_u128(uint64_t a, uint64_t b) {
    return absl::MakeUint128(a, b);
  }
#endif

  static inline constexpr state_type multiplier() {
    return make_u128(kMultA, kMultB);
  }
  static inline constexpr state_type increment() {
    return make_u128(kIncA, kIncB);
  }
};

// Implementation of the PCG xsl_rr_128_64 128-bit mixing function, which
// accepts an input of state_type and mixes it into an output of result_type.
struct pcg_xsl_rr_128_64 {
#if ABSL_HAVE_INTRINSIC_INT128
  using state_type = __uint128_t;
#else
  using state_type = absl::uint128;
#endif
  using result_type = uint64_t;

  inline uint64_t operator()(state_type state) {
    // This is equivalent to the xsl_rr_128_64 mixing function.
#if ABSL_HAVE_INTRINSIC_INT128
    uint64_t rotate = static_cast<uint64_t>(state >> 122u);
    state ^= state >> 64;
    uint64_t s = static_cast<uint64_t>(state);
#else
    uint64_t h = Uint128High64(state);
    uint64_t rotate = h >> 58u;
    uint64_t s = Uint128Low64(state) ^ h;
#endif
    return random_internal::rotr(s, rotate);
  }
};

// Parameterized implementation of the PCG 64-bit oneseq state.
// This provides state_type, multiplier, and increment for pcg_engine.
template <uint64_t kMult, uint64_t kInc>
class pcg64_params {
 public:
  using state_type = uint64_t;
  static inline constexpr state_type multiplier() { return kMult; }
  static inline constexpr state_type increment() { return kInc; }
};

// Implementation of the PCG xsh_rr_64_32 64-bit mixing function, which accepts
// an input of state_type and mixes it into an output of result_type.
struct pcg_xsh_rr_64_32 {
  using state_type = uint64_t;
  using result_type = uint32_t;
  inline uint32_t operator()(uint64_t state) {
    return random_internal::rotr(
        static_cast<uint32_t>(((state >> 18) ^ state) >> 27), state >> 59);
  }
};

// Stable pcg_engine implementations:
// This is a 64-bit generator using 128-bits of state.
// The output sequence is equivalent to Melissa O'Neil's pcg64_oneseq.
using pcg64_2018_engine = pcg_engine<
    random_internal::pcg128_params<0x2360ed051fc65da4ull, 0x4385df649fccf645ull,
                                   0x5851f42d4c957f2d, 0x14057b7ef767814f>,
    random_internal::pcg_xsl_rr_128_64>;

// This is a 32-bit generator using 64-bits of state.
// This is equivalent to Melissa O'Neil's pcg32_oneseq.
using pcg32_2018_engine = pcg_engine<
    random_internal::pcg64_params<0x5851f42d4c957f2dull, 0x14057b7ef767814full>,
    random_internal::pcg_xsh_rr_64_32>;

}  // namespace random_internal
ABSL_NAMESPACE_END
}  // namespace absl

#endif  // ABSL_RANDOM_INTERNAL_PCG_ENGINE_H_