uniform_real_distribution.h 7.16 KB
Newer Older
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
// Copyright 2017 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.
//
// -----------------------------------------------------------------------------
// File: uniform_real_distribution.h
// -----------------------------------------------------------------------------
//
// This header defines a class for representing a uniform floating-point
// distribution over a half-open interval [a,b). You use this distribution in
// combination with an Abseil random bit generator to produce random values
// according to the rules of the distribution.
//
// `absl::uniform_real_distribution` is a drop-in replacement for the C++11
// `std::uniform_real_distribution` [rand.dist.uni.real] but is considerably
// faster than the libstdc++ implementation.
//
// Note: the standard-library version may occasionally return `1.0` when
// default-initialized. See https://bugs.llvm.org//show_bug.cgi?id=18767
// `absl::uniform_real_distribution` does not exhibit this behavior.

#ifndef ABSL_RANDOM_UNIFORM_REAL_DISTRIBUTION_H_
#define ABSL_RANDOM_UNIFORM_REAL_DISTRIBUTION_H_

#include <cassert>
#include <cmath>
#include <cstdint>
#include <istream>
#include <limits>
#include <type_traits>

#include "absl/meta/type_traits.h"
#include "absl/random/internal/fast_uniform_bits.h"
#include "absl/random/internal/generate_real.h"
#include "absl/random/internal/iostream_state_saver.h"

namespace absl {
ABSL_NAMESPACE_BEGIN

// absl::uniform_real_distribution<T>
//
// This distribution produces random floating-point values uniformly distributed
// over the half-open interval [a, b).
//
// Example:
//
//   absl::BitGen gen;
//
//   // Use the distribution to produce a value between 0.0 (inclusive)
//   // and 1.0 (exclusive).
//   double value = absl::uniform_real_distribution<double>(0, 1)(gen);
//
template <typename RealType = double>
class uniform_real_distribution {
 public:
  using result_type = RealType;

  class param_type {
   public:
    using distribution_type = uniform_real_distribution;

    explicit param_type(result_type lo = 0, result_type hi = 1)
        : lo_(lo), hi_(hi), range_(hi - lo) {
      // [rand.dist.uni.real] preconditions 2 & 3
      assert(lo <= hi);
      // NOTE: For integral types, we can promote the range to an unsigned type,
      // which gives full width of the range. However for real (fp) types, this
      // is not possible, so value generation cannot use the full range of the
      // real type.
      assert(range_ <= (std::numeric_limits<result_type>::max)());
      assert(std::isfinite(range_));
    }

    result_type a() const { return lo_; }
    result_type b() const { return hi_; }

    friend bool operator==(const param_type& a, const param_type& b) {
      return a.lo_ == b.lo_ && a.hi_ == b.hi_;
    }

    friend bool operator!=(const param_type& a, const param_type& b) {
      return !(a == b);
    }

   private:
    friend class uniform_real_distribution;
    result_type lo_, hi_, range_;

    static_assert(std::is_floating_point<RealType>::value,
                  "Class-template absl::uniform_real_distribution<> must be "
                  "parameterized using a floating-point type.");
  };

  uniform_real_distribution() : uniform_real_distribution(0) {}

  explicit uniform_real_distribution(result_type lo, result_type hi = 1)
      : param_(lo, hi) {}

  explicit uniform_real_distribution(const param_type& param) : param_(param) {}

  // uniform_real_distribution<T>::reset()
  //
  // Resets the uniform real distribution. Note that this function has no effect
  // because the distribution already produces independent values.
  void reset() {}

  template <typename URBG>
  result_type operator()(URBG& gen) {  // NOLINT(runtime/references)
    return operator()(gen, param_);
  }

  template <typename URBG>
  result_type operator()(URBG& gen,  // NOLINT(runtime/references)
                         const param_type& p);

  result_type a() const { return param_.a(); }
  result_type b() const { return param_.b(); }

  param_type param() const { return param_; }
  void param(const param_type& params) { param_ = params; }

  result_type(min)() const { return a(); }
  result_type(max)() const { return b(); }

  friend bool operator==(const uniform_real_distribution& a,
                         const uniform_real_distribution& b) {
    return a.param_ == b.param_;
  }
  friend bool operator!=(const uniform_real_distribution& a,
                         const uniform_real_distribution& b) {
    return a.param_ != b.param_;
  }

 private:
  param_type param_;
  random_internal::FastUniformBits<uint64_t> fast_u64_;
};

// -----------------------------------------------------------------------------
// Implementation details follow
// -----------------------------------------------------------------------------
template <typename RealType>
template <typename URBG>
typename uniform_real_distribution<RealType>::result_type
uniform_real_distribution<RealType>::operator()(
    URBG& gen, const param_type& p) {  // NOLINT(runtime/references)
  using random_internal::GeneratePositiveTag;
  using random_internal::GenerateRealFromBits;
  using real_type =
      absl::conditional_t<std::is_same<RealType, float>::value, float, double>;

  while (true) {
    const result_type sample =
        GenerateRealFromBits<real_type, GeneratePositiveTag, true>(
            fast_u64_(gen));
    const result_type res = p.a() + (sample * p.range_);
    if (res < p.b() || p.range_ <= 0 || !std::isfinite(p.range_)) {
      return res;
    }
    // else sample rejected, try again.
  }
}

template <typename CharT, typename Traits, typename RealType>
std::basic_ostream<CharT, Traits>& operator<<(
    std::basic_ostream<CharT, Traits>& os,  // NOLINT(runtime/references)
    const uniform_real_distribution<RealType>& x) {
  auto saver = random_internal::make_ostream_state_saver(os);
  os.precision(random_internal::stream_precision_helper<RealType>::kPrecision);
  os << x.a() << os.fill() << x.b();
  return os;
}

template <typename CharT, typename Traits, typename RealType>
std::basic_istream<CharT, Traits>& operator>>(
    std::basic_istream<CharT, Traits>& is,     // NOLINT(runtime/references)
    uniform_real_distribution<RealType>& x) {  // NOLINT(runtime/references)
  using param_type = typename uniform_real_distribution<RealType>::param_type;
  using result_type = typename uniform_real_distribution<RealType>::result_type;
  auto saver = random_internal::make_istream_state_saver(is);
  auto a = random_internal::read_floating_point<result_type>(is);
  if (is.fail()) return is;
  auto b = random_internal::read_floating_point<result_type>(is);
  if (!is.fail()) {
    x.param(param_type(a, b));
  }
  return is;
}
ABSL_NAMESPACE_END
}  // namespace absl

#endif  // ABSL_RANDOM_UNIFORM_REAL_DISTRIBUTION_H_