为测试生成强烈偏向的随机数
Posted
技术标签:
【中文标题】为测试生成强烈偏向的随机数【英文标题】:Generating strongly biased random numbers for tests 【发布时间】:2012-09-27 17:59:44 【问题描述】:我想使用随机输入运行测试,并且需要生成“合理”随机 数字,即匹配得足够好以通过测试函数的数字 先决条件,但希望在其代码内部造成更严重的破坏。
math.random()
(我正在使用 Lua)产生均匀分布的随机
数字。扩大这些规模将产生比小数字更多的大数字,
而且整数会很少。
我想歪曲随机数(或使用旧的生成新的
作为随机源)以强烈支持“简单”数字的方式,
但仍将覆盖整个范围,即扩展到正/负无穷大
(或±1e309
为double
)。这意味着:
不同的描述:固定一个基本概率 x 使得概率
将总和为 1 并将数字 n 的概率定义为 xk
其中 k 是 n 被构造为超现实的一代
号码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
永远不会精确表示。
【问题讨论】:
需要注意的一点是,浮点数在其范围内不是均匀分布的。 【参考方案1】:算法怎么样?
-
使用库函数在 (0, 1) 中生成随机浮点数
根据所需的概率密度函数生成一个随机整数舍入点(例如,0 代表概率 0.5,1 代表概率 0.25,2 代表概率 0.125,...)。
按该舍入点“舍入”浮点数(例如
floor((float_val << roundoff)+0.5)
)
根据另一个 PDF 生成一个随机积分指数(例如 0、1、2、3,每个概率为 0.1,然后递减)
将四舍五入的浮点数乘以 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 个超现实数是:
转换为二进制:
1000 dec = 1111101000 箱
1 成为加号,0 成为减号:
1111101000
+++++-+---
第一个'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
【讨论】:
以上是关于为测试生成强烈偏向的随机数的主要内容,如果未能解决你的问题,请参考以下文章