diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 1cd8b11334..cd0b4db07c 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -1815,6 +1815,27 @@ repeat('Pg', 4) PgPgPgPg + + + + random_normal + + + random_normal ( + mean double precision + , stddev double precision ) + double precision + + + Returns a random value from a normal distribution, with default + mean of 0.0 and default stddev of 1.0. + + + random_normal(0.0, 1.0) + 0.051285419 + + + @@ -1824,7 +1845,8 @@ repeat('Pg', 4) PgPgPgPg void - Sets the seed for subsequent random() calls; + Sets the seed for subsequent random() and + random_normal() calls; argument must be between -1.0 and 1.0, inclusive diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql index 52517a6531..e645c2c7a6 100644 --- a/src/backend/catalog/system_functions.sql +++ b/src/backend/catalog/system_functions.sql @@ -620,6 +620,13 @@ CREATE OR REPLACE FUNCTION STABLE PARALLEL SAFE AS 'sql_localtimestamp'; +CREATE OR REPLACE FUNCTION + random_normal(mean float8 DEFAULT 0.0, stddev float8 DEFAULT 1.0) +RETURNS float8 +LANGUAGE INTERNAL +STRICT VOLATILE PARALLEL SAFE +AS 'drandom_normal'; + -- -- The default permissions for functions mean that anyone can execute them. -- A number of functions shouldn't be executable by just anyone, but rather diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c index b02a19be24..36a9712b0e 100644 --- a/src/backend/utils/adt/float.c +++ b/src/backend/utils/adt/float.c @@ -2715,14 +2715,9 @@ datanh(PG_FUNCTION_ARGS) } -/* - * drandom - returns a random number - */ -Datum -drandom(PG_FUNCTION_ARGS) +static void +drandom_check_default_seed(void) { - float8 result; - /* Initialize random seed, if not done yet in this process */ if (unlikely(!drandom_seed_set)) { @@ -2742,6 +2737,17 @@ drandom(PG_FUNCTION_ARGS) } drandom_seed_set = true; } +} + +/* + * drandom - returns a random number + */ +Datum +drandom(PG_FUNCTION_ARGS) +{ + float8 result; + + drandom_check_default_seed(); /* pg_prng_double produces desired result range [0.0 - 1.0) */ result = pg_prng_double(&drandom_seed); @@ -2749,6 +2755,35 @@ drandom(PG_FUNCTION_ARGS) PG_RETURN_FLOAT8(result); } +/* + * drandom_normal - returns a random number from a normal distribution + * + */ +Datum +drandom_normal(PG_FUNCTION_ARGS) +{ + float8 z, result; + float8 mean = 0.0; + float8 stddev = 1.0; + + /* Read optional stddev */ + if (PG_NARGS() >= 2) + stddev = PG_GETARG_FLOAT8(1); + + /* Read optional mean */ + if (PG_NARGS() >= 1) + mean = PG_GETARG_FLOAT8(0); + + drandom_check_default_seed(); + + /* Get random value from standard normal(mean = 0.0, stddev = 1.0) */ + z = pg_prng_double_normal(&drandom_seed); + /* Transform the normal standard variable (z) */ + /* using the target normal distribution parameters */ + result = (stddev * z) + mean; + + PG_RETURN_FLOAT8(result); +} /* * setseed - set seed for the random number generator diff --git a/src/common/pg_prng.c b/src/common/pg_prng.c index 3d2f42724e..23bdb235ab 100644 --- a/src/common/pg_prng.c +++ b/src/common/pg_prng.c @@ -19,10 +19,13 @@ #include "c.h" +#include /* for DBL_EPSILON */ #include /* for ldexp() */ #include "common/pg_prng.h" #include "port/pg_bitutils.h" +#include "utils/float.h" + /* process-wide state vector */ pg_prng_state pg_global_prng_state; @@ -245,3 +248,30 @@ pg_prng_bool(pg_prng_state *state) return (bool) (v >> 63); } + + +/* + * Select a random double from the normal distribution with + * mean = 0.0 and stddev = 1.0. + * + * To get a result for a different normal distribution use + * STDDEV * pg_prng_double_normal + MEAN + * + * Using https://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform + */ +double +pg_prng_double_normal(pg_prng_state *state) +{ + double u1, u2, z0; + /* Ensure u1 is at least as big as epsilon */ + do + { + u1 = pg_prng_double(state); + } + while (u1 <= DBL_EPSILON); + u2 = pg_prng_double(state); + + /* Apply Box-Muller calculation for one normal-valued output */ + z0 = sqrt(-2.0 * log(u1)) * cos(2.0 * M_PI * u2); + return z0; +} diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 719599649a..b923db6fda 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -3362,6 +3362,10 @@ { oid => '1599', descr => 'set random seed', proname => 'setseed', provolatile => 'v', proparallel => 'r', prorettype => 'void', proargtypes => 'float8', prosrc => 'setseed' }, +{ oid => '5151', descr => 'random value from normal distribution', + proname => 'random_normal', provolatile => 'v', proparallel => 'r', + prorettype => 'float8', proargtypes => 'float8 float8', + proargnames => '{mean,stddev}', prosrc => 'drandom_normal' }, # OIDS 1600 - 1699 diff --git a/src/include/common/pg_prng.h b/src/include/common/pg_prng.h index d9895b495c..5b3ef7cd83 100644 --- a/src/include/common/pg_prng.h +++ b/src/include/common/pg_prng.h @@ -55,6 +55,7 @@ extern uint32 pg_prng_uint32(pg_prng_state *state); extern int32 pg_prng_int32(pg_prng_state *state); extern int32 pg_prng_int32p(pg_prng_state *state); extern double pg_prng_double(pg_prng_state *state); +extern double pg_prng_double_normal(pg_prng_state *state); extern bool pg_prng_bool(pg_prng_state *state); #endif /* PG_PRNG_H */ diff --git a/src/test/regress/expected/random.out b/src/test/regress/expected/random.out index a919b28d8d..784001480b 100644 --- a/src/test/regress/expected/random.out +++ b/src/test/regress/expected/random.out @@ -51,3 +51,31 @@ SELECT AVG(random) FROM RANDOM_TBL ----- (0 rows) +-- now test the random_normal() +TRUNCATE random_tbl; +INSERT INTO random_tbl (random) + SELECT count(*) + FROM onek WHERE random_normal(0, 1) < 0; +INSERT INTO random_tbl (random) + SELECT count(*) + FROM onek WHERE random_normal(0, 1) < 0; +INSERT INTO random_tbl (random) + SELECT count(*) + FROM onek WHERE random_normal(0, 1) < 0; +INSERT INTO random_tbl (random) + SELECT count(*) + FROM onek WHERE random_normal(0, 1) < 0; +-- expect similar, but not identical values +SELECT random, count(random) FROM random_tbl + GROUP BY random HAVING count(random) > 3; + random | count +--------+------- +(0 rows) + +-- approximately check expected distribution +SELECT AVG(random) FROM random_tbl + HAVING AVG(random) NOT BETWEEN 400 AND 600; + avg +----- +(0 rows) + diff --git a/src/test/regress/sql/random.sql b/src/test/regress/sql/random.sql index 8187b2c288..4e0a91c3e4 100644 --- a/src/test/regress/sql/random.sql +++ b/src/test/regress/sql/random.sql @@ -42,3 +42,28 @@ SELECT random, count(random) FROM RANDOM_TBL SELECT AVG(random) FROM RANDOM_TBL HAVING AVG(random) NOT BETWEEN 80 AND 120; + +-- now test the random_normal() +TRUNCATE random_tbl; +INSERT INTO random_tbl (random) + SELECT count(*) + FROM onek WHERE random_normal(0, 1) < 0; +INSERT INTO random_tbl (random) + SELECT count(*) + FROM onek WHERE random_normal(0, 1) < 0; +INSERT INTO random_tbl (random) + SELECT count(*) + FROM onek WHERE random_normal(0, 1) < 0; +INSERT INTO random_tbl (random) + SELECT count(*) + FROM onek WHERE random_normal(0, 1) < 0; + +-- expect similar, but not identical values +SELECT random, count(random) FROM random_tbl + GROUP BY random HAVING count(random) > 3; + +-- approximately check expected distribution +SELECT AVG(random) FROM random_tbl + HAVING AVG(random) NOT BETWEEN 400 AND 600; + +