"interval is empty",Lua的math.random无法处理大数字?

我不知道这是Lua自身的错误还是我做错了什么。我在使用Lua for Windows (Lua 5.1.4):

>return math.random(0, 1000000000)
1251258

这会返回0到10000000000之间的随机整数,与预期一致。这似乎对所有其他值都有效。但是,如果我加一个0:

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

任何大于那个值的数字都会出现同样的问题。

我试图找出到底需要多高的数字才会导致这种情况,发现了更奇怪的东西:

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

如果这个值是2147483647,那么它会给我负数。任何比这更高的值都会报错。任何比这更低的数字都可以正常工作。

这是二进制中的0b1111111111111111111111111111111,确切地说是31个二进制数字。不过我不确定这意味着什么。

点赞
用户234175
用户234175

这个意外的行为(bug?) 是由于 Lua 5.1 中 math.random 如何处理输入参数导致的。从 lmathlib.c 中可以看到:

case 2: {  /* 底部和上限 */
  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);  /* 在 `l' 和 `u' 之间的 int */
  break;
}

你可能知道,在 C 中,标准的 int 可以表示 -2,147,483,6482,147,483,647 的值。像你的用例中那样将 +1 添加到 2,147,483,647 会导致溢出并将值 回绕-2,147,483,648。最终结果是负数,因为你正数与负数相乘。

此外,超过 2,147,483,647 的任何数都会由于溢出回绕而导致 luaL_argcheck 失败。

有几种方法可以解决这个问题:

  • 升级到 Lua 5.2。该版本已通过将输入参数视为 lua_Number 来解决此问题。
  • 切换到 LuaJIT,它没有这个整数溢出问题。
  • 使用修复程序对 Lua 5.1 源代码进行修改并重新编译。
  • 修改随机范围,使其不会溢出。
2013-11-24 06:14:32
用户869951
用户869951

如果你需要比随机函数支持的范围(32位有符号整数或2^31,因为math.random在C级别)更大但小于Lua“number”类型的范围(基于What is the maximum value of a number in Lua?,2^52,甚至可能是2^53),你可以尝试生成两个随机数:将第一个缩放到所需范围;使用第二个“填充间隙”。例如,假设你想要0到2^36的范围。从math.random得到的最大值是2^31。因此,你可以这样做:

-- 2^36 = 2^31 * 2^5 这样
scale = 2^5
baseRand = scale * math.random(0, 2^31)
-- baseRand 现在在 0 到 2^36 的范围内但是在可能的值集合中存在 2^5 的间隙; 用第二个随机数填充间隙:
fillGap = math.random(0, 2^5)
randNum = baseRand + fillGap

只要所需范围小于Lua解释器对Lua数字的最大值,这将起作用,该值是可配置的编译时参数,但如果你使用股票构建,则为2^52,一个非常大的数字(虽然不像最大长整数2^63那样大)。

还要注意最大正N位整数为2^N-1(不是2^N),但上述技术可应用于任何范围,例如scale = 10^6 ,则randNum = 10^6 * math.random(0, 10^8) + math.random(0, 10^6)。

2013-11-24 14:46:15