125 lines
3.4 KiB
C
125 lines
3.4 KiB
C
/*
|
|
* A tiny random number generator used for Math.random() and other internals.
|
|
*
|
|
* Default algorithm is xoroshiro128+: http://xoroshiro.di.unimi.it/xoroshiro128plus.c
|
|
* with SplitMix64 seed preparation: http://xorshift.di.unimi.it/splitmix64.c.
|
|
*
|
|
* Low memory targets and targets without 64-bit types use a slightly smaller
|
|
* (but slower) algorithm by Adi Shamir:
|
|
* http://www.woodmann.com/forum/archive/index.php/t-3100.html.
|
|
*
|
|
*/
|
|
|
|
#include "duk_internal.h"
|
|
|
|
#if !defined(DUK_USE_GET_RANDOM_DOUBLE)
|
|
|
|
#if defined(DUK_USE_PREFER_SIZE) || !defined(DUK_USE_64BIT_OPS)
|
|
#define DUK__RANDOM_SHAMIR3OP
|
|
#else
|
|
#define DUK__RANDOM_XOROSHIRO128PLUS
|
|
#endif
|
|
|
|
#if defined(DUK__RANDOM_SHAMIR3OP)
|
|
#define DUK__UPDATE_RND(rnd) do { \
|
|
(rnd) += ((rnd) * (rnd)) | 0x05UL; \
|
|
(rnd) = ((rnd) & 0xffffffffUL); /* if duk_uint32_t is exactly 32 bits, this is a NOP */ \
|
|
} while (0)
|
|
|
|
#define DUK__RND_BIT(rnd) ((rnd) >> 31) /* only use the highest bit */
|
|
|
|
DUK_INTERNAL void duk_util_tinyrandom_prepare_seed(duk_hthread *thr) {
|
|
DUK_UNREF(thr); /* Nothing now. */
|
|
}
|
|
|
|
DUK_INTERNAL duk_double_t duk_util_tinyrandom_get_double(duk_hthread *thr) {
|
|
duk_double_t t;
|
|
duk_small_int_t n;
|
|
duk_uint32_t rnd;
|
|
|
|
rnd = thr->heap->rnd_state;
|
|
|
|
n = 53; /* enough to cover the whole mantissa */
|
|
t = 0.0;
|
|
|
|
do {
|
|
DUK__UPDATE_RND(rnd);
|
|
t += DUK__RND_BIT(rnd);
|
|
t /= 2.0;
|
|
} while (--n);
|
|
|
|
thr->heap->rnd_state = rnd;
|
|
|
|
DUK_ASSERT(t >= (duk_double_t) 0.0);
|
|
DUK_ASSERT(t < (duk_double_t) 1.0);
|
|
|
|
return t;
|
|
}
|
|
#endif /* DUK__RANDOM_SHAMIR3OP */
|
|
|
|
#if defined(DUK__RANDOM_XOROSHIRO128PLUS)
|
|
DUK_LOCAL DUK_ALWAYS_INLINE duk_uint64_t duk__rnd_splitmix64(duk_uint64_t *x) {
|
|
duk_uint64_t z;
|
|
z = (*x += DUK_U64_CONSTANT(0x9E3779B97F4A7C15));
|
|
z = (z ^ (z >> 30U)) * DUK_U64_CONSTANT(0xBF58476D1CE4E5B9);
|
|
z = (z ^ (z >> 27U)) * DUK_U64_CONSTANT(0x94D049BB133111EB);
|
|
return z ^ (z >> 31U);
|
|
}
|
|
|
|
DUK_LOCAL DUK_ALWAYS_INLINE duk_uint64_t duk__rnd_rotl(const duk_uint64_t x, duk_small_uint_t k) {
|
|
return (x << k) | (x >> (64U - k));
|
|
}
|
|
|
|
DUK_LOCAL DUK_ALWAYS_INLINE duk_uint64_t duk__xoroshiro128plus(duk_uint64_t *s) {
|
|
duk_uint64_t s0;
|
|
duk_uint64_t s1;
|
|
duk_uint64_t res;
|
|
|
|
s0 = s[0];
|
|
s1 = s[1];
|
|
res = s0 + s1;
|
|
s1 ^= s0;
|
|
s[0] = duk__rnd_rotl(s0, 55) ^ s1 ^ (s1 << 14U);
|
|
s[1] = duk__rnd_rotl(s1, 36);
|
|
|
|
return res;
|
|
}
|
|
|
|
DUK_INTERNAL void duk_util_tinyrandom_prepare_seed(duk_hthread *thr) {
|
|
duk_small_uint_t i;
|
|
duk_uint64_t x;
|
|
|
|
/* Mix both halves of the initial seed with SplitMix64. The intent
|
|
* is to ensure that very similar raw seeds (which is usually the case
|
|
* because current seed is Date.now()) result in different xoroshiro128+
|
|
* seeds.
|
|
*/
|
|
x = thr->heap->rnd_state[0]; /* Only [0] is used as input here. */
|
|
for (i = 0; i < 64; i++) {
|
|
thr->heap->rnd_state[i & 0x01] = duk__rnd_splitmix64(&x); /* Keep last 2 values. */
|
|
}
|
|
}
|
|
|
|
DUK_INTERNAL duk_double_t duk_util_tinyrandom_get_double(duk_hthread *thr) {
|
|
duk_uint64_t v;
|
|
duk_double_union du;
|
|
|
|
/* For big and little endian the integer and IEEE double byte order
|
|
* is the same so a direct assignment works. For mixed endian the
|
|
* 32-bit parts must be swapped.
|
|
*/
|
|
v = (DUK_U64_CONSTANT(0x3ff) << 52U) | (duk__xoroshiro128plus((duk_uint64_t *) thr->heap->rnd_state) >> 12U);
|
|
du.ull[0] = v;
|
|
#if defined(DUK_USE_DOUBLE_ME)
|
|
do {
|
|
duk_uint32_t tmp;
|
|
tmp = du.ui[0];
|
|
du.ui[0] = du.ui[1];
|
|
du.ui[1] = tmp;
|
|
} while (0);
|
|
#endif
|
|
return du.d - 1.0;
|
|
}
|
|
#endif /* DUK__RANDOM_XOROSHIRO128PLUS */
|
|
|
|
#endif /* !DUK_USE_GET_RANDOM_DOUBLE */
|