blob: 36e0e83105ef8d60298b1a31decd532a1127357c [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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.
// Date: Thu Dec 31 13:35:39 CST 2015
#include <limits> // numeric_limits
#include <math.h>
#include "butil/basictypes.h"
#include "butil/macros.h"
#include "butil/time.h" // gettimeofday_us()
#include "butil/fast_rand.h"
namespace butil {
// This state can be seeded with any value.
typedef uint64_t SplitMix64Seed;
// A very fast generator passing BigCrush. Due to its 64-bit state, it's
// solely for seeding xorshift128+.
inline uint64_t splitmix64_next(SplitMix64Seed* seed) {
uint64_t z = (*seed += UINT64_C(0x9E3779B97F4A7C15));
z = (z ^ (z >> 30)) * UINT64_C(0xBF58476D1CE4E5B9);
z = (z ^ (z >> 27)) * UINT64_C(0x94D049BB133111EB);
return z ^ (z >> 31);
}
// xorshift128+ is the fastest generator passing BigCrush without systematic
// failures
inline uint64_t xorshift128_next(FastRandSeed* seed) {
uint64_t s1 = seed->s[0];
const uint64_t s0 = seed->s[1];
seed->s[0] = s0;
s1 ^= s1 << 23; // a
seed->s[1] = s1 ^ s0 ^ (s1 >> 18) ^ (s0 >> 5); // b, c
return seed->s[1] + s0;
}
// seed xorshift128+ with splitmix64.
void init_fast_rand_seed(FastRandSeed* seed) {
SplitMix64Seed seed4seed = butil::gettimeofday_us();
seed->s[0] = splitmix64_next(&seed4seed);
seed->s[1] = splitmix64_next(&seed4seed);
}
inline uint64_t fast_rand_impl(uint64_t range, FastRandSeed* seed) {
// Separating uint64_t values into following intervals:
// [0,range-1][range,range*2-1] ... [uint64_max/range*range,uint64_max]
// If the generated 64-bit random value falls into any interval except the
// last one, the probability of taking any value inside [0, range-1] is
// same. If the value falls into last interval, we retry the process until
// the value falls into other intervals. If min/max are limited to 32-bits,
// the retrying is rare. The amortized retrying count at maximum is 1 when
// range equals 2^32. A corner case is that even if the range is power of
// 2(e.g. min=0 max=65535) in which case the retrying can be avoided, we
// still retry currently. The reason is just to keep the code simpler
// and faster for most cases.
const uint64_t div = std::numeric_limits<uint64_t>::max() / range;
uint64_t result;
do {
result = xorshift128_next(seed) / div;
} while (result >= range);
return result;
}
// Seeds for different threads are stored separately in thread-local storage.
static __thread FastRandSeed _tls_seed = { { 0, 0 } };
// True if the seed is (probably) uninitialized. There's definitely false
// positive, but it's OK for us.
inline bool need_init(const FastRandSeed& seed) {
return seed.s[0] == 0 && seed.s[1] == 0;
}
uint64_t fast_rand() {
if (need_init(_tls_seed)) {
init_fast_rand_seed(&_tls_seed);
}
return xorshift128_next(&_tls_seed);
}
uint64_t fast_rand(FastRandSeed* seed) {
return xorshift128_next(seed);
}
uint64_t fast_rand_less_than(uint64_t range) {
if (range == 0) {
return 0;
}
if (need_init(_tls_seed)) {
init_fast_rand_seed(&_tls_seed);
}
return fast_rand_impl(range, &_tls_seed);
}
int64_t fast_rand_in_64(int64_t min, int64_t max) {
if (need_init(_tls_seed)) {
init_fast_rand_seed(&_tls_seed);
}
if (min >= max) {
if (min == max) {
return min;
}
const int64_t tmp = min;
min = max;
max = tmp;
}
int64_t range = max - min + 1;
if (range == 0) {
// max = INT64_MAX, min = INT64_MIN
return (int64_t)xorshift128_next(&_tls_seed);
}
return min + (int64_t)fast_rand_impl(max - min + 1, &_tls_seed);
}
uint64_t fast_rand_in_u64(uint64_t min, uint64_t max) {
if (need_init(_tls_seed)) {
init_fast_rand_seed(&_tls_seed);
}
if (min >= max) {
if (min == max) {
return min;
}
const uint64_t tmp = min;
min = max;
max = tmp;
}
uint64_t range = max - min + 1;
if (range == 0) {
// max = UINT64_MAX, min = UINT64_MIN
return xorshift128_next(&_tls_seed);
}
return min + fast_rand_impl(range, &_tls_seed);
}
inline double fast_rand_double(FastRandSeed* seed) {
// Copied from rand_util.cc
COMPILE_ASSERT(std::numeric_limits<double>::radix == 2, otherwise_use_scalbn);
static const int kBits = std::numeric_limits<double>::digits;
uint64_t random_bits = xorshift128_next(seed) & ((UINT64_C(1) << kBits) - 1);
double result = ldexp(static_cast<double>(random_bits), -1 * kBits);
return result;
}
double fast_rand_double() {
if (need_init(_tls_seed)) {
init_fast_rand_seed(&_tls_seed);
}
return fast_rand_double(&_tls_seed);
}
void fast_rand_bytes(void* output, size_t output_length) {
const size_t n = output_length / 8;
for (size_t i = 0; i < n; ++i) {
static_cast<uint64_t*>(output)[i] = fast_rand();
}
const size_t m = output_length - n * 8;
if (m) {
uint8_t* p = static_cast<uint8_t*>(output) + n * 8;
uint64_t r = fast_rand();
for (size_t i = 0; i < m; ++i) {
p[i] = (r & 0xFF);
r = (r >> 8);
}
}
}
std::string fast_rand_printable(size_t length) {
std::string result(length, 0);
const size_t halflen = length/2;
fast_rand_bytes(&result[0], halflen);
for (size_t i = 0; i < halflen; ++i) {
const uint8_t b = result[halflen - 1 - i];
result[length - 1 - 2*i] = 'A' + (b & 0xF);
result[length - 2 - 2*i] = 'A' + (b >> 4);
}
if (halflen * 2 != length) {
result[0] = 'A' + (fast_rand() % 16);
}
return result;
}
} // namespace butil