Skip to content

Commit 06b0b4b

Browse files
committed
patch 8.1.2342: random number generator in Vim script is slow
Problem: Random number generator in Vim script is slow. Solution: Add rand() and srand(). (Yasuhiro Matsumoto, closes #1277)
1 parent 67a2deb commit 06b0b4b

File tree

5 files changed

+163
-0
lines changed

5 files changed

+163
-0
lines changed

runtime/doc/eval.txt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2648,6 +2648,7 @@ pumvisible() Number whether popup menu is visible
26482648
pyeval({expr}) any evaluate |Python| expression
26492649
py3eval({expr}) any evaluate |python3| expression
26502650
pyxeval({expr}) any evaluate |python_x| expression
2651+
rand([{expr}]) Number get pseudo-random number
26512652
range({expr} [, {max} [, {stride}]])
26522653
List items from {expr} to {max}
26532654
readdir({dir} [, {expr}]) List file names in {dir} selected by {expr}
@@ -2761,6 +2762,7 @@ spellsuggest({word} [, {max} [, {capital}]])
27612762
split({expr} [, {pat} [, {keepempty}]])
27622763
List make |List| from {pat} separated {expr}
27632764
sqrt({expr}) Float square root of {expr}
2765+
srand([{expr}]) List get seed for |rand()|
27642766
state([{what}]) String current state of Vim
27652767
str2float({expr}) Float convert String to Float
27662768
str2list({expr} [, {utf8}]) List convert each character of {expr} to
@@ -7637,6 +7639,20 @@ range({expr} [, {max} [, {stride}]]) *range()*
76377639
Can also be used as a |method|: >
76387640
GetExpr()->range()
76397641
<
7642+
7643+
rand([{expr}]) *rand()*
7644+
Return a pseudo-random Number generated with an xorshift
7645+
algorithm using seed {expr}. {expr} can be initialized by
7646+
|srand()| and will be updated by rand().
7647+
If {expr} is omitted, an internal seed value is used and
7648+
updated.
7649+
7650+
Examples: >
7651+
:echo rand()
7652+
:let seed = srand()
7653+
:echo rand(seed)
7654+
:echo rand(seed)
7655+
<
76407656
*readdir()*
76417657
readdir({directory} [, {expr}])
76427658
Return a list with file and directory names in {directory}.
@@ -9130,6 +9146,19 @@ sqrt({expr}) *sqrt()*
91309146
{only available when compiled with the |+float| feature}
91319147

91329148

9149+
srand([{expr}]) *srand()*
9150+
Initialize seed used by |rand()|:
9151+
- If {expr} is not given, seed values are initialized by
9152+
time(NULL) a.k.a. epoch time.
9153+
- If {expr} is given, return seed values which x element is
9154+
{expr}. This is useful for testing or when a predictable
9155+
sequence is expected.
9156+
9157+
Examples: >
9158+
:let seed = srand()
9159+
:let seed = srand(userinput)
9160+
:echo rand(seed)
9161+
91339162
state([{what}]) *state()*
91349163
Return a string which contains characters indicating the
91359164
current state. Mostly useful in callbacks that want to do

src/evalfunc.c

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ static void f_pyeval(typval_T *argvars, typval_T *rettv);
169169
#if defined(FEAT_PYTHON) || defined(FEAT_PYTHON3)
170170
static void f_pyxeval(typval_T *argvars, typval_T *rettv);
171171
#endif
172+
static void f_rand(typval_T *argvars, typval_T *rettv);
172173
static void f_range(typval_T *argvars, typval_T *rettv);
173174
static void f_reg_executing(typval_T *argvars, typval_T *rettv);
174175
static void f_reg_recording(typval_T *argvars, typval_T *rettv);
@@ -225,6 +226,7 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv);
225226
static void f_split(typval_T *argvars, typval_T *rettv);
226227
#ifdef FEAT_FLOAT
227228
static void f_sqrt(typval_T *argvars, typval_T *rettv);
229+
static void f_srand(typval_T *argvars, typval_T *rettv);
228230
static void f_str2float(typval_T *argvars, typval_T *rettv);
229231
#endif
230232
static void f_str2list(typval_T *argvars, typval_T *rettv);
@@ -634,6 +636,7 @@ static funcentry_T global_functions[] =
634636
#if defined(FEAT_PYTHON) || defined(FEAT_PYTHON3)
635637
{"pyxeval", 1, 1, FEARG_1, f_pyxeval},
636638
#endif
639+
{"rand", 0, 1, FEARG_1, f_rand},
637640
{"range", 1, 3, FEARG_1, f_range},
638641
{"readdir", 1, 2, FEARG_1, f_readdir},
639642
{"readfile", 1, 3, FEARG_1, f_readfile},
@@ -725,6 +728,7 @@ static funcentry_T global_functions[] =
725728
{"split", 1, 3, FEARG_1, f_split},
726729
#ifdef FEAT_FLOAT
727730
{"sqrt", 1, 1, FEARG_1, f_sqrt},
731+
{"srand", 0, 1, FEARG_1, f_srand},
728732
#endif
729733
{"state", 0, 1, FEARG_1, f_state},
730734
#ifdef FEAT_FLOAT
@@ -5128,6 +5132,79 @@ f_pyxeval(typval_T *argvars, typval_T *rettv)
51285132
}
51295133
#endif
51305134

5135+
/*
5136+
* "rand()" function
5137+
*/
5138+
static void
5139+
f_rand(typval_T *argvars, typval_T *rettv)
5140+
{
5141+
list_T *l = NULL;
5142+
UINT32_T x, y, z, w, t;
5143+
static int rand_seed_initialized = FALSE;
5144+
static UINT32_T xyzw[4] = {123456789, 362436069, 521288629, 88675123};
5145+
5146+
#define SHUFFLE_XORSHIFT128 \
5147+
t = x ^ (x << 11); \
5148+
x = y; y = z; z = w; \
5149+
w = (w ^ (w >> 19)) ^ (t ^ (t >> 8));
5150+
5151+
if (argvars[0].v_type == VAR_UNKNOWN)
5152+
{
5153+
// When argument is not given, return random number initialized
5154+
// statically.
5155+
if (!rand_seed_initialized)
5156+
{
5157+
xyzw[0] = (varnumber_T)time(NULL);
5158+
rand_seed_initialized = TRUE;
5159+
}
5160+
5161+
x = xyzw[0];
5162+
y = xyzw[1];
5163+
z = xyzw[2];
5164+
w = xyzw[3];
5165+
SHUFFLE_XORSHIFT128;
5166+
xyzw[0] = x;
5167+
xyzw[1] = y;
5168+
xyzw[2] = z;
5169+
xyzw[3] = w;
5170+
}
5171+
else if (argvars[0].v_type == VAR_LIST)
5172+
{
5173+
listitem_T *lx, *ly, *lz, *lw;
5174+
5175+
l = argvars[0].vval.v_list;
5176+
if (list_len(l) != 4)
5177+
goto theend;
5178+
5179+
lx = list_find(l, 0L);
5180+
ly = list_find(l, 1L);
5181+
lz = list_find(l, 2L);
5182+
lw = list_find(l, 3L);
5183+
if (lx->li_tv.v_type != VAR_NUMBER) goto theend;
5184+
if (ly->li_tv.v_type != VAR_NUMBER) goto theend;
5185+
if (lz->li_tv.v_type != VAR_NUMBER) goto theend;
5186+
if (lw->li_tv.v_type != VAR_NUMBER) goto theend;
5187+
x = (UINT32_T)lx->li_tv.vval.v_number;
5188+
y = (UINT32_T)ly->li_tv.vval.v_number;
5189+
z = (UINT32_T)lz->li_tv.vval.v_number;
5190+
w = (UINT32_T)lw->li_tv.vval.v_number;
5191+
SHUFFLE_XORSHIFT128;
5192+
lx->li_tv.vval.v_number = (varnumber_T)x;
5193+
ly->li_tv.vval.v_number = (varnumber_T)y;
5194+
lz->li_tv.vval.v_number = (varnumber_T)z;
5195+
lw->li_tv.vval.v_number = (varnumber_T)w;
5196+
}
5197+
else
5198+
goto theend;
5199+
5200+
rettv->v_type = VAR_NUMBER;
5201+
rettv->vval.v_number = (varnumber_T)w;
5202+
return;
5203+
5204+
theend:
5205+
semsg(_(e_invarg2), tv_get_string(&argvars[0]));
5206+
}
5207+
51315208
/*
51325209
* "range()" function
51335210
*/
@@ -7012,6 +7089,31 @@ f_sqrt(typval_T *argvars, typval_T *rettv)
70127089
rettv->vval.v_float = 0.0;
70137090
}
70147091

7092+
/*
7093+
* "srand()" function
7094+
*/
7095+
static void
7096+
f_srand(typval_T *argvars, typval_T *rettv)
7097+
{
7098+
if (rettv_list_alloc(rettv) == FAIL)
7099+
return;
7100+
if (argvars[0].v_type == VAR_UNKNOWN)
7101+
list_append_number(rettv->vval.v_list, (varnumber_T)vim_time());
7102+
else
7103+
{
7104+
int error = FALSE;
7105+
UINT32_T x = (UINT32_T)tv_get_number_chk(&argvars[0], &error);
7106+
7107+
if (error)
7108+
return;
7109+
7110+
list_append_number(rettv->vval.v_list, x);
7111+
}
7112+
list_append_number(rettv->vval.v_list, 362436069);
7113+
list_append_number(rettv->vval.v_list, 521288629);
7114+
list_append_number(rettv->vval.v_list, 88675123);
7115+
}
7116+
70157117
/*
70167118
* "str2float()" function
70177119
*/

src/testdir/Make_all.mak

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ NEW_TESTS = \
211211
test_pyx3 \
212212
test_quickfix \
213213
test_quotestar \
214+
test_random \
214215
test_recover \
215216
test_regex_char_classes \
216217
test_regexp_latin \
@@ -403,6 +404,7 @@ NEW_TESTS_RES = \
403404
test_pyx3.res \
404405
test_quickfix.res \
405406
test_quotestar.res \
407+
test_random.res \
406408
test_regex_char_classes.res \
407409
test_registers.res \
408410
test_restricted.res \

src/testdir/test_random.vim

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
" Tests for srand() and rand()
2+
3+
func Test_Rand()
4+
let r = srand(123456789)
5+
call assert_equal([123456789, 362436069, 521288629, 88675123], r)
6+
call assert_equal(3701687786, rand(r))
7+
call assert_equal(458299110, rand(r))
8+
call assert_equal(2500872618, rand(r))
9+
call assert_equal(3633119408, rand(r))
10+
call assert_equal(516391518, rand(r))
11+
12+
call test_settime(12341234)
13+
let s = srand()
14+
call assert_equal(s, srand())
15+
call test_settime(12341235)
16+
call assert_notequal(s, srand())
17+
18+
call srand()
19+
let v = rand()
20+
call assert_notequal(v, rand())
21+
22+
call assert_fails('echo srand([1])', 'E745:')
23+
call assert_fails('echo rand([1, 2, 3])', 'E475:')
24+
call assert_fails('echo rand([[1], 2, 3, 4])', 'E475:')
25+
call assert_fails('echo rand([1, [2], 3, 4])', 'E475:')
26+
call assert_fails('echo rand([1, 2, [3], 4])', 'E475:')
27+
call assert_fails('echo rand([1, 2, 3, [4]])', 'E475:')
28+
endfunc

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,8 @@ static char *(features[]) =
737737

738738
static int included_patches[] =
739739
{ /* Add new patch number below this line */
740+
/**/
741+
2342,
740742
/**/
741743
2341,
742744
/**/

0 commit comments

Comments
 (0)