lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Hi,

In Lua5.3.0-work2 math.random does not support the max integer value. A range larger than the max integer is also not supported [1]. Reading Lua source code that's something new in lmathlib.c due the new integer type [2].

Is there a reason for that or is it something in the Lua5.3.0 release TODO list ?

In case it can help you can find in attachement a patch that fix (almost) all the value range issues [3].

Thanks in advance for you answer,
Best regards,

  Cyril Romain

[1] Lua 5.3.0 (work2):
> max_int = 2^(debug.numbits'i'-1)-1
> return print(math.random(0, max_int))
stdin:1: bad argument #1 to 'random' (interval is empty)
> return print(math.random(-42, max_int-42))
stdin:1: bad argument #1 to 'random' (interval is empty)

[2] lmathlib.c:
up++;  /* change interval to [low, up) */

[3] As explained in the patch header, corners cases still exists with a narrow range around max integer values. The tests in the test script are far from exhaustive but provided for convenience.
>From 38cc79bf8a902e935388789f6005db78a95c51d5 Mon Sep 17 00:00:00 2001
From: Cyril Romain <public.cyril@romain.tf>
Date: Sun, 6 Apr 2014 11:09:34 +0200
Subject: [PATCH] Improve math.random

math.random now:
 - accepts min and max integer values:
   i.e. min=-2^(debug.numbits'i'-1) and max=2^(debug.numbits'i'-1)-1.
 - accepts a range larger than the max integer value:
   e.g. math.random(-5000, 2^(debug.numbits'i'-1)-1)
 - is "symetric to 0", i.e. it does floor positive values and ceil
   negative ones:
   e.g. math.random(2.5, 2.5) == 3, math.random(-2.5, -2.5) == -3

Corner cases still exists with a narrow range around the max integer.
---
 src/lmathlib.c | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/src/lmathlib.c b/src/lmathlib.c
index a40a6fb..2623ab8 100644
--- a/src/lmathlib.c
+++ b/src/lmathlib.c
@@ -218,7 +218,7 @@ static int math_random (lua_State *L) {
   /* the `%' avoids the (rare) case of r==1, and is needed also because on
      some systems (SunOS!) `rand()' may return a value larger than RAND_MAX */
   lua_Number r = (lua_Number)(rand()%RAND_MAX) / (lua_Number)RAND_MAX;
-  lua_Integer low, up;
+  lua_Number low, up;
   switch (lua_gettop(L)) {  /* check number of arguments */
     case 0: {  /* no arguments */
       lua_pushnumber(L, r);  /* Number between 0 and 1 */
@@ -226,20 +226,21 @@ static int math_random (lua_State *L) {
     }
     case 1: {  /* only upper limit */
       low = 1;
-      up = luaL_checkinteger(L, 1);
+      up = luaL_checknumber(L, 1);
       break;
     }
     case 2: {  /* lower and upper limits */
-      low = luaL_checkinteger(L, 1);
-      up = luaL_checkinteger(L, 2);
+      low = luaL_checknumber(L, 1);
+      up = luaL_checknumber(L, 2);
       break;
     }
     default: return luaL_error(L, "wrong number of arguments");
   }
   /* random integer in the interval [low, up] */
-  up++;  /* change interval to [low, up) */
-  luaL_argcheck(L, up - low > 0, 1, "interval is empty");
-  lua_pushinteger(L, (lua_Integer)(r * (lua_Number)(up - low)) + low);
+  luaL_argcheck(L, low <= up, 1, "interval is empty");
+  lua_Number ri = r * up - r * low + low;
+  lua_Integer res = (lua_Integer)((ri < 0) ? l_mathop(ceil)(ri - 0.5) : l_mathop(floor)(ri + 0.5));
+  lua_pushinteger(L, res);
   return 1;
 }
 
-- 
1.9.1

min = -2^(debug.numbits'i'-1)
max = 2^(debug.numbits'i'-1)-1
local t = {min, max, 0, 1, -1, -2, 2, 5000, -809}
math.randomseed(os.time())
for i=1,200 do
	assert(math.random(-1, -1) == -1)
	assert(math.random(-2, -2) == -2)
	assert(math.random(1, 1) == 1)
	a = math.random(0, 1)
	assert(a == 1 or a == 0)
	assert(math.random(2.5, 2.5) == 3)
	assert(math.random(-2.5, -2.5) == -3)
	assert(math.random(min, min) == min)
	--assert(math.random(max, max) == max) this one fails
	m, n = math.random(7), math.random(7)
	if t[n] >= t[m] then
		a = math.random(t[m], t[n])
		print('rand('..t[m]..','..t[n]..") -> "..a)
		assert(a >= t[m] and a <= t[n])
	end
	m, n = 1, 2
	a = math.random(min, max)
	print('rand('..t[m]..','..t[n]..") -> "..a)
	assert(a >= t[m] and a <= t[n])
end