Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"interval is empty", Lua math.random isn't working for large numbers?

Tags:

random

lua

I didn't know if this is a bug in Lua itself or if I was doing something wrong. I couldn't find anything about it anywhere. I am using Lua for Windows (Lua 5.1.4):

>return math.random(0, 1000000000)
1251258

This returns a random integer between 0 and 10000000000, as expected. This seems to work for all other values. But if I add a single 0:

>return math.random(0, 10000000000)
stdin:1: bad argument #2 to 'random' (interval is empty)

Any number higher than that does the same thing.

I tried to figure out exactly how high a number has to be to cause this and found something even weirder:

>return math.random(0, 2147483647)
-75617745

If the value is 2147483647 then it gives me negative numbers. Any higher than that and it throws an error. Any lower than that and it works fine.

That's 0b1111111111111111111111111111111 in binary, 31 binary digits exactly. I am not sure what that means though.

like image 263
Houshalter Avatar asked Nov 24 '13 04:11

Houshalter


2 Answers

This unexpected behavior (bug?) is due to how math.random treats the input arguments passed in Lua 5.1. From lmathlib.c:

case 2: {  /* lower and upper limits */
  int l = luaL_checkint(L, 1);
  int u = luaL_checkint(L, 2);
  luaL_argcheck(L, l<=u, 2, "interval is empty");
  lua_pushnumber(L, floor(r*(u-l+1))+l);  /* int between `l' and `u' */
  break;
}

As you may know in C, a standard int can represent values -2,147,483,648 to 2,147,483,647. Adding +1 to 2,147,483,647, like in your use-case, will overflow and wrap around the value giving -2,147,483,648. The end result is negative since you're multiplying a positive with a negative number.

Furthermore, anything above 2,147,483,647 will fail the luaL_argcheck due to overflow wraparound.

There are a few ways to address this problem:

  • Upgrade to Lua 5.2. That one has since fixed this issue by treating the input arguments as lua_Number instead.
  • Switch to LuaJIT which does not have this integer overflow issue.
  • Patch the Lua 5.1 source yourself with the fix and recompile.
  • Modify your random range so it does not overflow.
like image 142
greatwolf Avatar answered Sep 30 '22 10:09

greatwolf


If you need a range that is larger than what the random function supports (32 bit signed integers or 2^31 due to sign bit, because math.random is at C level), but smaller than the range of Lua "number" type (based on What is the maximum value of a number in Lua?, 2^52, or maybe even 2^53), you could try generating two random numbers: scale the first to the range desired; add the second to "fill the gap". For example, say you want a range of 0 to 2^36. The largest from math.random is 2^31. So you could do:

-- 2^36 = 2^31 * 2^5 so
scale = 2^5
baseRand = scale * math.random(0, 2^31)
-- baseRand is now between 0 and 2^36 but there are gaps of 2^5 in the set 
-- of possible values; fill the gaps with second random number: 
fillGap = math.random(0, 2^5)
randNum = baseRand + fillGap

This will work as long as the desired range is less than the Lua interpreter's maximum for Lua numbers, which is a configurable compile time parameter but if you use stock build it is 2^52, a very large number (although not as large as largest long integer, 2^63).

Note also that largest positive N-bit integer is 2^N-1 (not 2^N), but the above technique can be applied to any range, you could have for instance scale = 10^6 then randNum = 10^6 * math.random(0, 10^8) + math.random(0, 10^6).

like image 23
Oliver Avatar answered Sep 30 '22 10:09

Oliver