【问题标题】:Generating strongly biased random numbers for tests为测试生成强烈偏向的随机数
【发布时间】:2012-09-27 17:59:44
【问题描述】:

我想使用随机输入运行测试,并且需要生成“合理”随机 数字,即匹配得足够好以通过测试函数的数字 先决条件,但希望在其代码内部造成更严重的破坏。

math.random()(我正在使用 Lua)产生均匀分布的随机 数字。扩大这些规模将产生比小数字更多的大数字, 而且整数会很少。

我想歪曲随机数(或使用旧的生成新的 作为随机源)以强烈支持“简单”数字的方式, 但仍将覆盖整个范围,即扩展到正/负无穷大 (或±1e309double)。这意味着:

  • 最多十个数字应该是最常见的,
  • 整数应该比分数更常见,
  • 以 0.5 结尾的数字应该是最常见的分数,
  • 后跟 0.25 和 0.75;然后是 0.125,
  • 等等。

不同的描述:固定一个基本概率 x 使得概率 将总和为 1 并将数字 n 的概率定义为 xk 其中 kn 被构造为超现实的一代 号码1。将 x 分配给 0,将 x2 分配给 -1 和 +1, x3 到 -2、-1/2、+1/2 和 +2,依此类推。这 很好地描述了接近我想要的东西(它也有点歪斜 很多),但几乎无法用于计算随机数。所结果的 分布不是连续的(它是分形的!),我不知道如何 确定基本概率x(我认为对于无限精度,它会是 零),并且基于此通过迭代计算数字非常糟糕 慢(花费近乎无限的时间来构造大量数字)。

有谁知道一个简单的近似值,给定一个均匀分布的 随机源,产生非常粗略分布的随机数 如上所述?

我想运行数千个随机测试,数量/速度更多 比质量重要。尽管如此,更好的数字意味着更少的输入被拒绝。

Lua 有 JIT,所以性能通常不是什么大问题。然而,基于跳跃 随机性会破坏每一个预测,以及对math.random()的许多调用 也会很慢。这意味着封闭式公式将优于 迭代或递归。


1 维基百科有一个article on surreal numbers,其中 nice picture。一个超现实的数字是一对两个超现实的 数字,即x := {n|m},其值为中间的数字 对,即(对于有限数){n|m} = (n+m)/2(作为有理数)。如果一侧 对是空的,这被解释为增量(或减量,如果正确 是空的)减一。如果两边都是空的,那就是零。最初,有 没有数字,所以唯一可以建立的数字是0 := { | }。在一代 二一可以建立数字{0| } =: 1{ |0} =: -1,三我们得到 {1| } =: 2{|1} =: -2{0|1} =: 1/2{-1|0} =: -1/2(加上一些 已知数字的更复杂的表示,例如{-1|1} ? 0)。注意 例如1/3 永远不会由有限数生成,因为它是无限的 分数 - 浮点数也是如此,1/3 永远不会精确表示。

【问题讨论】:

  • 需要注意的一点是,浮点数在其范围内不是均匀分布的。

标签: random lua automated-tests distribution


【解决方案1】:

算法怎么样?

  1. 使用库函数在 (0, 1) 中生成随机浮点数
  2. 根据所需的概率密度函数生成一个随机整数舍入点(例如,0 代表概率 0.5,1 代表概率 0.25,2 代表概率 0.125,...)。
  3. 按该舍入点“舍入”浮点数(例如floor((float_val << roundoff)+0.5)
  4. 根据另一个 PDF 生成一个随机积分指数(例如 0、1、2、3,每个概率为 0.1,然后递减)
  5. 将四舍五入的浮点数乘以 2exponent

【讨论】:

    【解决方案2】:

    对于类似超现实的十进制扩展,您需要一个随机二进制数。 偶数位告诉你是停止还是继续,奇数位告诉你在树上是向右还是向左:

    > 0... => 0.0 [50%] Stop
    > 100... => -0.5 [<12.5%] Go, Left, Stop
    > 110... => 0.5 [<12.5%] Go, Right, Stop
    > 11100... => 0.25 [<3.125%] Go, Right, Go, Left, Stop
    > 11110... => 0.75 [<3.125%] Go, Right, Go, Right, Stop
    > 1110100... => 0.125
    > 1110110... => 0.375
    > 1111100... => 0.625
    > 1111110... => 0.875
    

    快速生成随机二进制数的一种方法是查看 math.random() 中的十进制数字,并将 0-4 替换为“1”,将 5-9 替换为“1”:

    • 0.8430419054348022 变成 1000001010001011 变成-0.5

    • 0.5513009827118367 变成 1100001101001011 变成0.25 等等

    没有做过太多的lua编程,但是用Javascript你可以做到:

    Math.random().toString().substring(2).split("").map(
        function(digit) { return digit >= "5" ? 1 : 0 }
    );
    

    或真正的二进制扩展:

    Math.random().toString(2).substring(2)
    

    不确定哪个更“随机”——您需要对其进行测试。

    可以以这种方式生成超现实数,但大多数结果将是 a/2^b 形式的小数,整数相对较少。在第 3 天,只产生 2 个整数(-3 和 3)与 6 个小数,第 4 天是 2 对 14,第 n 天是 2 对 (2^n-2)。

    如果你从math.random() 添加两个均匀随机数,你会得到一个新的分布,它具有类似“三角形”的分布(从中心线性递减)。添加 3 或更多将获得更多的“钟形曲线”,例如以 0 为中心的分布:

    math.random() + math.random() + math.random()  - 1.5
    

    除以一个随机数会得到一个真正的百搭数:

    A/(math.random()+1e-300)
    

    这将返回 A 和 (理论上) A*1e+300 之间的结果, 虽然我的测试表明 50% 的时间结果在 A 和 2*A 之间 大约 75% 的时间在 A 和 4*A 之间。

    将它们放在一起,我们得到:

    round(6*(math.random()+math.random()+math.random() - 1.5)/(math.random()+1e-300))
    

    这有超过 70% 的数字在 -9 和 9 之间返回,并且很少出现一些大数字。

    请注意,此分布的平均值和总和将趋向于向较大的负数或正数发散,因为您运行它的次数越多,分母中的小数就越有可能导致数字“爆炸”到一个很大的数字,例如 147,967 或 -194,137。

    请参阅gist 以获取示例代码。

    Josh

    【讨论】:

      【解决方案3】:

      您可以立即计算出第 n 个出生的超现实数。

      例如,第 1000 个超现实数是:

      1. 转换为二进制:

        1000 dec = 1111101000 箱

      2. 1 成为加号,0 成为减号:

        1111101000

        +++++-+---

      3. 第一个'1'位是0值,下一组相似的数字是+1(代表1)或-1(代表0),那么值是1/2、1/4、1/8,等每个后续位。

        1 1 1 1 1 0 1 0 0 0

        + + + + + - + - - -

        0 1 1 1 1 h h h h h

        +0+1+1+1+1-1/2+1/4-1/8-1/16-1/32

        = 3+17/32

        = 113/32

        = 3.53125

      此表示形式的二进制长度等于该数字的诞生日期。

      超现实数的左右数是二进制表示,其尾部分别被剥离回最后的 0 或 1。

      超现实数字在 -1 和 1 之间均匀分布,其中一半的特定日期创建的数字将存在。 1/4 的数字均匀分布在 -2 到 -1 和 1 到 2 之间,依此类推。最大范围将为与您提供的天数匹配的负整数到正整数。这些数字慢慢地趋于无穷大,因为每天只会在负数和正数范围内增加一个,而日期包含的数字是上一天的两倍。

      编辑:

      这个位表示的一个好名字是“sinary”

      负数是换位。例如:

      100010101001101s -> negative number (always start 10...)
      
      111101010110010s -> positive number (always start 01...)
      

      我们注意到所有位翻转都接受第一个转置位。

      Nan 是 => 0(因为所有其他数字都以 1 开头),这使得它非常适合在计算机中的位寄存器中表示,因为需要前导零(我们不再制造三进制计算机......太糟糕了)

      所有康威超现实代数都可以在这些数字上完成,无需转换为二进制或十进制。

      正弦格式可以看作是一个简单的计数器加上一个 2 的补码十进制表示。

      这里是关于finary的不完整报告(类似于sinary):https://github.com/peawormsworth/tools/blob/master/finary/Fine%20binary.ipynb

      【讨论】:

        猜你喜欢
        • 2013-03-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-05
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多