我无法理解这个,哪个更随机?
rand()
OR:
rand() * rand()
我发现这是一个真正的脑筋急转弯,你能帮我吗?
编辑:
直觉上,我知道数学上的答案是它们是同样随机的,但我不禁想,如果你在将两者相乘时“运行随机数算法”两次,你会创建比只做一次更随机的东西。
我无法理解这个,哪个更随机?
rand()
OR:
rand() * rand()
我发现这是一个真正的脑筋急转弯,你能帮我吗?
编辑:
直觉上,我知道数学上的答案是它们是同样随机的,但我不禁想,如果你在将两者相乘时“运行随机数算法”两次,你会创建比只做一次更随机的东西。
当前回答
公认的答案很好,但有另一种方法可以回答你的问题。PachydermPuncher的答案已经采用了这种替代方法,我只是将其扩展一点。
思考信息理论最简单的方法是用最小的信息单位,一个比特。
在C标准库中,rand()返回一个0到rand_MAX范围内的整数,根据平台的不同,这个限制可能会有不同的定义。假设RAND_MAX恰好被定义为2^n-1,其中n是某个整数(这恰好是Microsoft实现中的情况,其中n为15)。然后我们可以说,一个好的实现将返回n位信息。
想象一下,rand()通过翻转硬币找到一位的值来构造随机数,然后重复直到它有一批15位。然后,这些位是独立的(任何一个位的值都不会影响同一批中其他位具有特定值的可能性)。因此,独立考虑的每个比特都像一个介于0和1之间的随机数,并且在该范围内“均匀分布”(可能是0和1)。
位的独立性确保了由一批位表示的数字也将在其范围内均匀分布。这很明显:如果有15位,允许的范围是0到2^15-1=32767。该范围内的每个数字都是唯一的位模式,例如:
010110101110010
并且如果比特是独立的,则没有模式比任何其他模式更可能发生。因此,该范围内所有可能的数字都有相同的可能性。反之亦然:如果rand()产生均匀分布的整数,那么这些数字是由独立的位组成的。
因此,将rand()看作是一条生产比特的生产线,它恰好以任意大小的批量提供比特。如果您不喜欢大小,请将批分成单独的位,然后按您喜欢的数量将它们放回一起(尽管如果您需要的特定范围不是2的幂,则需要缩小数字,目前最简单的方法是转换为浮点)。
回到你最初的建议,假设你想从15个批次到30个批次,向rand()请求第一个数字,将其移位15位,然后向其添加另一个rand(()。这是一种在不影响均匀分布的情况下组合对rand(的两个调用的方法。它的工作原理很简单,因为放置信息位的位置之间没有重叠。
这与通过乘以常数来“拉伸”rand()的范围非常不同。例如,如果你想将rand()的范围加倍,你可以乘以2,但现在你只能得到偶数,而不能得到奇数!这并不完全是一个平稳的分布,并且可能是一个严重的问题,具体取决于应用程序,例如,假设允许奇数/偶数下注的轮盘游戏。(从位的角度考虑,你可以直观地避免这个错误,因为你会意识到,乘以2等于将位向左移动一位(意义更大),然后用零填补空白。所以很明显,信息量是一样的——只是移动了一点。)
在浮点数应用程序中,数字范围中的这种差距是无法解决的,因为浮点数范围内在地具有根本无法表示的差距:在每两个可表示的浮点数之间的差距中存在无限数量的缺失实数!所以无论如何,我们必须学会与差距共处。
正如其他人所警告的那样,直觉在这一领域是有风险的,特别是因为数学家无法抵抗实数的诱惑,因为实数是一种充满了粗糙的无限和明显的悖论的可怕的令人困惑的东西。
但至少如果你从比特角度来看,你的直觉可能会让你走得更远。比特真的很容易——甚至计算机都能理解。
其他回答
用更离散的数字来考虑可能会有所帮助。考虑一下要生成1到36之间的随机数,所以您决定最简单的方法是投掷两个公平的6面骰子。你得到了这个:
1 2 3 4 5 6
-----------------------------
1| 1 2 3 4 5 6
2| 2 4 6 8 10 12
3| 3 6 9 12 15 18
4| 4 8 12 16 20 24
5| 5 10 15 20 25 30
6| 6 12 18 24 30 36
所以我们有36个数字,但并不是所有数字都得到了公平的表示,有些数字根本没有出现。靠近中心对角线(左下角到右上角)的数字将以最高频率出现。
描述骰子之间不公平分布的相同原则同样适用于0.0和1.0之间的浮点数。
假设你有一个简单的硬币翻转问题,偶数被认为是正面,奇数被认为是反面。逻辑实现是:
rand() mod 2
在足够大的分布范围内,偶数的数量应该等于奇数的数量。
现在考虑一个小小的调整:
rand() * rand() mod 2
如果其中一个结果是偶数,那么整个结果应该是偶数。考虑4种可能的结果(偶*偶=偶,偶*奇=偶,奇*偶=偶数,奇*奇=奇数)。现在,在足够大的分布范围内,答案应该是75%的时间。
如果我是你,我敢打赌。
这条评论实际上更多的是解释为什么不应该基于您的方法实现自定义随机函数,而不是讨论随机性的数学财产。
两者都不是“更随机”的。
rand()基于伪随机种子生成一组可预测的数字(通常基于当前时间,该时间总是在变化)。将序列中的两个连续数字相乘,生成一个不同但同样可预测的数字序列。
关于这是否会减少冲突,答案是否定的。它实际上会增加冲突,这是因为在0<n<1的情况下,两个数字相乘的结果。结果将是一个较小的分数,导致结果偏向频谱的低端。
一些进一步的解释。在下文中,“不可预测”和“随机”是指某人根据先前的数字猜测下一个数字的能力,即预言。
给定生成以下值列表的种子x:
0.3, 0.6, 0.2, 0.4, 0.8, 0.1, 0.7, 0.3, ...
rand()将生成上述列表,rand(*rand)将生成:
0.18, 0.08, 0.08, 0.21, ...
这两种方法将始终为同一种子生成相同的数字列表,因此预言者同样可以预测。但是如果你看一下两个调用相乘的结果,你会发现它们都在0.3以下,尽管在原始序列中分布良好。由于两个分数相乘的影响,这些数字是有偏差的。由此产生的数字总是较小,因此更可能发生碰撞,尽管仍然无法预测。
假设rand()返回一个介于[0,1)之间的数字,很明显rand(*rand)将偏向于0。这是因为将x乘以[0,1)之间的数字将得到一个小于x的数字。下面是10000个随机数的分布:
google.charts.load(“current”,{packages:[“corechart”]});google.cacharts.setOnLoadCallback(drawChart);函数drawChart(){变量i;var randomNumbers=[];对于(i=0;i<10000;i++){randomNumbers.push(Math.rrandom()*Math.random());}var chart=新的google.visability.Histogram(document.getElementById(“chart-1”));var data=新的google.visibility.DataTable();data.addColumn(“数字”,“值”);randomNumbers.forEach(函数(randomNumber){data.addRow([randomNumber]);});chart.draw(数据{title:randomNumbers.length+“rand()*rand(值介于[0,1)之间”,图例:{位置:“无”}});}<script src=“https://www.gstatic.com/charts/loader.js“></script><div id=“chart-1”style=“height:500px”>正在生成图表</分区>
如果rand()返回[x,y]之间的整数,则得到以下分布。注意奇数与偶数的数量:
google.charts.load(“current”,{packages:[“corechart”]});google.cacharts.setOnLoadCallback(drawChart);document.querySelector(“#绘制图表”).addEventListener(“单击”,绘制图表);函数randomInt(最小值,最大值){return Math.floor(Math.random()*(max-min+1))+min;}函数drawChart(){var min=编号(document.querySelector(“#rand min”).value);var max=编号(document.querySelector(“#rand max”).value);如果(最小值>=最大值){回来}变量i;var randomNumbers=[];对于(i=0;i<10000;i++){randomNumbers.push(randomInt(最小,最大)*randomInt(最小,最小));}var chart=新的google.visability.Histogram(document.getElementById(“chart-1”));var data=新的google.visibility.DataTable();data.addColumn(“数字”,“值”);randomNumbers.forEach(函数(randomNumber){data.addRow([randomNumber]);});chart.draw(数据{title:randomNumbers.length+“rand()*rand(()值介于[“+min+”,“+max+”]”之间,图例:{位置:“无”},直方图:{bucketSize:1}});}<script src=“https://www.gstatic.com/charts/loader.js“></script><input-type=“number”id=“rand-min”value=“0”min=“0“max=“10”><input type=“number”id=“rand max”value=“9”min=“0”max=“10”><input type=“button”id=“draw chart”value=“Apply”><div id=“chart-1”style=“height:500px”>正在生成图表</分区>
大多数rand()实现都有一定的周期。也就是说,在大量的调用之后,序列会重复。rand()*rand(()的输出序列在一半时间内重复,因此在这个意义上它“不那么随机”。
此外,如果没有仔细的构造,对随机值执行算术往往会导致较少的随机性。上面的一张海报引用了“rand()+rand(()+rand()…”(例如,k倍),这实际上会倾向于rand(返回值范围的平均值的k倍。(这是一种随机行走,步数与平均值对称。)
具体来说,假设rand()函数返回[0,1)范围内的均匀分布随机实数。(是的,这个例子允许无限精度。这不会改变结果。)您没有选择特定的语言,不同的语言可能会做不同的事情,但以下分析适用于对rand()的任何非反常实现的修改。乘积rand()*rand(()也在[0,1)范围内,但不再均匀分布。事实上,乘积在区间[0,1/4)和区间[1/4,1)中的可能性一样大。更多的乘法将使结果进一步趋向于零。这使得结果更可预测。在广义上,更可预测的==更少的随机性。
几乎所有对均匀随机输入的操作序列都是非均匀随机的,从而提高了可预测性。小心的话,我们可以克服这一特性,但这样就可以更容易地在实际需要的范围内生成一个均匀分布的随机数,而不是在算术上浪费时间。