/*-------------------------------------------------------------------------
 *
 * Pseudo-Random Number Generator
 *
 * We use Blackman and Vigna's xoroshiro128** 1.0 algorithm
 * to have a small, fast PRNG suitable for generating reasonably
 * good-quality 64-bit data.  This should not be considered
 * cryptographically strong, however.
 *
 * About these generators: https://prng.di.unimi.it/
 * See also https://en.wikipedia.org/wiki/List_of_random_number_generators
 *
 * Copyright (c) 2021-2023, PostgreSQL Global Development Group
 *
 * src/common/pg_prng.c
 *
 *-------------------------------------------------------------------------
 */

#include "c.h"

#include <math.h>

#include "common/pg_prng.h"
#include "port/pg_bitutils.h"

/* X/Open (XSI) requires <math.h> to provide M_PI, but core POSIX does not */
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif


/* process-wide state vector */
pg_prng_state pg_global_prng_state;


/*
 * 64-bit rotate left
 */
static inline uint64
rotl(uint64 x, int bits)
{
	return (x << bits) | (x >> (64 - bits));
}

/*
 * The basic xoroshiro128** algorithm.
 * Generates and returns a 64-bit uniformly distributed number,
 * updating the state vector for next time.
 *
 * Note: the state vector must not be all-zeroes, as that is a fixed point.
 */
static uint64
xoroshiro128ss(pg_prng_state *state)
{
	uint64		s0 = state->s0,
				sx = state->s1 ^ s0,
				val = rotl(s0 * 5, 7) * 9;

	/* update state */
	state->s0 = rotl(s0, 24) ^ sx ^ (sx << 16);
	state->s1 = rotl(sx, 37);

	return val;
}

/*
 * We use this generator just to fill the xoroshiro128** state vector
 * from a 64-bit seed.
 */
static uint64
splitmix64(uint64 *state)
{
	/* state update */
	uint64		val = (*state += UINT64CONST(0x9E3779B97f4A7C15));

	/* value extraction */
	val = (val ^ (val >> 30)) * UINT64CONST(0xBF58476D1CE4E5B9);
	val = (val ^ (val >> 27)) * UINT64CONST(0x94D049BB133111EB);

	return val ^ (val >> 31);
}

/*
 * Initialize the PRNG state from a 64-bit integer,
 * taking care that we don't produce all-zeroes.
 */
void
pg_prng_seed(pg_prng_state *state, uint64 seed)
{
	state->s0 = splitmix64(&seed);
	state->s1 = splitmix64(&seed);
	/* Let's just make sure we didn't get all-zeroes */
	(void) pg_prng_seed_check(state);
}

/*
 * Initialize the PRNG state from a double in the range [-1.0, 1.0],
 * taking care that we don't produce all-zeroes.
 */
void
pg_prng_fseed(pg_prng_state *state, double fseed)
{
	/* Assume there's about 52 mantissa bits; the sign contributes too. */
	int64		seed = ((double) ((UINT64CONST(1) << 52) - 1)) * fseed;

	pg_prng_seed(state, (uint64) seed);
}

/*
 * Validate a PRNG seed value.
 */
bool
pg_prng_seed_check(pg_prng_state *state)
{
	/*
	 * If the seeding mechanism chanced to produce all-zeroes, insert
	 * something nonzero.  Anything would do; use Knuth's LCG parameters.
	 */
	if (unlikely(state->s0 == 0 && state->s1 == 0))
	{
		state->s0 = UINT64CONST(0x5851F42D4C957F2D);
		state->s1 = UINT64CONST(0x14057B7EF767814F);
	}

	/* As a convenience for the pg_prng_strong_seed macro, return true */
	return true;
}

/*
 * Select a random uint64 uniformly from the range [0, PG_UINT64_MAX].
 */
uint64
pg_prng_uint64(pg_prng_state *state)
{
	return xoroshiro128ss(state);
}

/*
 * Select a random uint64 uniformly from the range [rmin, rmax].
 * If the range is empty, rmin is always produced.
 */
uint64
pg_prng_uint64_range(pg_prng_state *state, uint64 rmin, uint64 rmax)
{
	uint64		val;

	if (likely(rmax > rmin))
	{
		/*
		 * Use bitmask rejection method to generate an offset in 0..range.
		 * Each generated val is less than twice "range", so on average we
		 * should not have to iterate more than twice.
		 */
		uint64		range = rmax - rmin;
		uint32		rshift = 63 - pg_leftmost_one_pos64(range);

		do
		{
			val = xoroshiro128ss(state) >> rshift;
		} while (val > range);
	}
	else
		val = 0;

	return rmin + val;
}

/*
 * Select a random int64 uniformly from the range [PG_INT64_MIN, PG_INT64_MAX].
 */
int64
pg_prng_int64(pg_prng_state *state)
{
	return (int64) xoroshiro128ss(state);
}

/*
 * Select a random int64 uniformly from the range [0, PG_INT64_MAX].
 */
int64
pg_prng_int64p(pg_prng_state *state)
{
	return (int64) (xoroshiro128ss(state) & UINT64CONST(0x7FFFFFFFFFFFFFFF));
}

/*
 * Select a random uint32 uniformly from the range [0, PG_UINT32_MAX].
 */
uint32
pg_prng_uint32(pg_prng_state *state)
{
	/*
	 * Although xoroshiro128** is not known to have any weaknesses in
	 * randomness of low-order bits, we prefer to use the upper bits of its
	 * result here and below.
	 */
	uint64		v = xoroshiro128ss(state);

	return (uint32) (v >> 32);
}

/*
 * Select a random int32 uniformly from the range [PG_INT32_MIN, PG_INT32_MAX].
 */
int32
pg_prng_int32(pg_prng_state *state)
{
	uint64		v = xoroshiro128ss(state);

	return (int32) (v >> 32);
}

/*
 * Select a random int32 uniformly from the range [0, PG_INT32_MAX].
 */
int32
pg_prng_int32p(pg_prng_state *state)
{
	uint64		v = xoroshiro128ss(state);

	return (int32) (v >> 33);
}

/*
 * Select a random double uniformly from the range [0.0, 1.0).
 *
 * Note: if you want a result in the range (0.0, 1.0], the standard way
 * to get that is "1.0 - pg_prng_double(state)".
 */
double
pg_prng_double(pg_prng_state *state)
{
	uint64		v = xoroshiro128ss(state);

	/*
	 * As above, assume there's 52 mantissa bits in a double.  This result
	 * could round to 1.0 if double's precision is less than that; but we
	 * assume IEEE float arithmetic elsewhere in Postgres, so this seems OK.
	 */
	return ldexp((double) (v >> (64 - 52)), -52);
}

/*
 * Select a random double from the normal distribution with
 * mean = 0.0 and stddev = 1.0.
 *
 * To get a result from a different normal distribution use
 *   STDDEV * pg_prng_double_normal() + MEAN
 *
 * Uses https://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform
 */
double
pg_prng_double_normal(pg_prng_state *state)
{
	double		u1,
				u2,
				z0;

	/*
	 * pg_prng_double generates [0, 1), but for the basic version of the
	 * Box-Muller transform the two uniformly distributed random numbers are
	 * expected to be in (0, 1]; in particular we'd better not compute log(0).
	 */
	u1 = 1.0 - pg_prng_double(state);
	u2 = 1.0 - pg_prng_double(state);

	/* Apply Box-Muller transform to get one normal-valued output */
	z0 = sqrt(-2.0 * log(u1)) * sin(2.0 * M_PI * u2);
	return z0;
}

/*
 * Select a random boolean value.
 */
bool
pg_prng_bool(pg_prng_state *state)
{
	uint64		v = xoroshiro128ss(state);

	return (bool) (v >> 63);
}
