我在尝试用JavaScript实现UUID生成器时遇到了这种奇怪的现象。
基本上,在JavaScript中,如果我使用节点Math.random()
上的内置4.2.2
生成大量随机数:
var records = {};
var l;
for (var i=0; i < 1e6; i += 1) {
l = String(Math.random()).length;
if (records[l]) {
records[l] += 1;
} else {
records[l] = 1;
}
}
console.log(records);
数字位数有一个奇怪的模式:
{ '12': 1,
'13': 11,
'14': 65,
'15': 663,
'16': 6619,
'17': 66378,
'18': 611441,
'19': 281175,
'20': 30379,
'21': 2939,
'22': 282,
'23': 44,
'24': 3 }
我认为这是V8的随机数生成器的怪癖,但Python 3.4.3
中出现了类似的模式:
12 : 2
13 : 5
14 : 64
15 : 672
16 : 6736
17 : 66861
18 : 610907
19 : 280945
20 : 30455
21 : 3129
22 : 224
Python代码如下:
import random
random.seed()
records = {}
for i in range(0, 1000000):
n = random.random()
l = len(str(n))
try:
records[l] += 1
except KeyError:
records[l] = 1;
for i in sorted(records):
print(i, ':', records[i])
预期从18到以下的模式:如果随机数应该有20个数字,那么如果数字的最后一个数字是0,它实际上只有19个数字。如果随机数生成器是好的,那么发生的概率大约是1/10。
但是为什么这种模式在19岁及以后才会逆转?
我想这与浮点数有关&#39;二进制表示,但我无法弄清楚原因。
答案 0 :(得分:8)
原因确实与浮点表示有关。浮点数表示具有它可以表示的最大(二进制)数字数和有限的指数值范围。现在当你不使用科学记数法打印出来时,你可能在某些情况下需要在有效数字开始跟随之后的小数点后面有一些零。
您可以通过打印转换为string
时长度最长的随机数来显示此效果:
var records = {};
var l, r;
for (var i=0; i < 1e6; i += 1) {
r = Math.random();
l = String(r).length;
if (l === 23) {
console.log(r);
}
if (records[l]) {
records[l] += 1;
} else {
records[l] = 1;
}
}
这只会打印23个长的字符串,你会得到这样的数字:
0.000007411070483631654
0.000053944830052166104
0.000018188989763578967
0.000029525788901141325
0.000009613635131744402
0.000005937417234758158
0.000021099748521158368
注意第一个非零数字之前的零。它们实际上并不存储在浮点表示的数字部分中,而是由其指数部分隐含。
如果您要取出前导零,然后进行计数:
var records = {};
var l, r, s;
for (var i=0; i < 1e6; i += 1) {
r = Math.random();
s = String(r).replace(/^[0\.]+/, '');
l = s.length;
if (records[l]) {
records[l] += 1;
} else {
records[l] = 1;
}
}
...你会得到不那么奇怪的结果。
但是,您会看到一些不规则性,原因是javascript
将小数字转换为string
:当它们变得太小时,科学记数法会用于string
表示。您可以通过以下脚本看到这一点(不确定每个浏览器是否都有相同的断点,因此您可能需要使用该数字):
var i = 0.00000123456789012345678;
console.log(String(i), String(i/10));
这给了我以下输出:
0.0000012345678901234567 1.2345678901234568e-7
因此,非常小的数字将获得更加固定的string
长度,通常为22个字符,而在非科学记数法中,长度为23是常见的。这也影响了我提供的第二个脚本,长度22将获得比23更多的命中。
应该注意的是,javascript
在以二进制表示形式转换为string
时不会切换到科学记数法:
var i = 0.1234567890123456789e-120;
console.log(i.toString(2));
以上将打印超过450个二进制数字的字符串!
答案 1 :(得分:2)
这是因为有些值是这样的:
0.00012345...
因此他们的时间更长。