这一页的 v1 到底读了时间戳的哪几位
这一页 v1 的时间戳段看着像 60 位,但逐毫秒跑下来,真正读到的只是一个 32 位窗口。generateV1 在第 126 到 162 行,第 131 行算出 ts = (now - 1582 纪元) * 10000,第 134、135、136 行分别取 ts & 0xFFFFFFFF、(ts >>> 32) & 0xFFFF、(ts >>> 48) & 0x0FFF。只针对本页、不外推。
移位取模 32:60 位塌成 28 个位置、24 个会变的位
>>> 的移位计数按 32 取模,于是 >>> 32 就是 >>> 0、>>> 48 就是 >>> 16。20000 个连续毫秒上 timeMid === (timeLow & 0xFFFF) 与 timeHi === ((timeLow >>> 16) & 0x0FFF) 都是 20000/20000:time_mid 是 time_low 低 16 位的重读,time_hi_and_version 低 12 位是同一窗口第 16 到 27 位的重读。三字段读到的位合起来只有位 0 到 27 共 28 个,位 32 以上没进代码。
会变的位比 28 还少:以 2000000 个毫秒步长扫描,按位或是 0xfffffff0、按位与是 0,恒 0 的是位 0 到 3;与被读到的 28 个位置取交集,真正会变的只剩 24 个位。第 138 行 timeHi | 0x1000 又把版本位钉成恒 1。
图 1:三处取值都在同一窗口上重读
恒 0 的末 nibble 落在第 8 个字符
时间戳恒为 16 的倍数:20000 个毫秒上 ts % 16 === 0 成立 20000/20000,而 ts % 32 的 0 与 16 各占 10000——「32 的倍数」只在半数毫秒成立。落到串上 time_low 段末位即第 8 个字符恒「0」,20000 采样与 100 条批次全中;反倒是最后一个字符(节点段末 nibble)在 16 个值上分布、取「0」只有 3/100。
timeLow 走 ToInt32:正负两条 leg 按毫秒翻转
timeLow = ts & 0xFFFFFFFF 是有符号 Int32,相邻毫秒里既给正也给负。整周期 = 2^32 / 10000 = 429496.7296 ms 即 7.1583 分钟,负值段 = 2^31 / 10000 = 214748.3648 ms 即 3.5791 分钟。真实未桩时钟第一次翻转只要 109614 ms;2400000 毫秒扫描负值占 1242928 步 = 51.79%,「按年才会负」只是抽样错觉。
图 2:负 leg 的周期与串形
负 leg 不改长度只改内容:深负 -68b10840 减号下标 [0,8,13,18,23],浅负 -1b80 因第 153 行 padStart(8,'0') 把 0 补在减号前、减号挪到下标 3。第 98 行 substr(20,12) 只消费前 32 个字符、丢掉节点段末 nibble,十六进制位只剩 31 个;1000 行批次长度分布 {36:1000}、每行减号个数 {5:1000}。
舍入的边界在 2^57,不在 2^53
当下 ts 已超 2^53 约 15.56 倍、落在 2^56 与 2^57 之间,这档 ULP 正好是 16;而 diff × 10000 恒为 16 的倍数(10000 = 16×625),乘积本就落在网格上,当下 1000 毫秒舍入 0 条。真正舍入要等 ts 跨过 2^57、ULP 变 32:桩到 2039-06-20T23:40:07.585Z 起 1000 毫秒舍入 499 条、2045-01-01 档 500 条;2^53 对应 1611 年,本页走不到。
输入真值表:数量框、开关与两枚掩码
| 输入项 | 实测口径 | 结果 |
|---|---|---|
| 数量框 17 种输入 | 三级兜底:先转整数,再 || 1,最后夹到 1 到 100 | 0、空串、"abc" 各 1 行回写 1;101、1000、1000000 各 100 行回写 100;"2.7" 截成 2 行 |
| 三枚开关 8 种组合 | 长度只由连字符与花括号决定,大小写不改长 | 32 / 34(加花括号)/ 36(加连字符)/ 38(两者都加)码元四种 |
| v4 两枚掩码 | 第 115、116 行 | 第 15 字符恒「4」100/100;第 20 字符落在 8/9/a/b,分布 {8:26, 9:23, a:29, b:22} |
nodeBytes[0] | 0x01 | 第 149 行置首字节最低位 | 落在节点段第 2 字符全为奇数 1000/1000;第 1 字符只有 517/1000 为奇数 |
批量与清场
桩在同一毫秒、count=100 时,v1 的 100 行前 14 个字符 distinct=1,差异全来自 clock_seq 的 4 字符与节点段 12 字符。真实时钟跑 1000 次 generateV1() 跨 14 ms,time_low 段 distinct=13、clock_seq 段 distinct=966、整串 1000。清场有残留:清空 lastUuids 后 renderList() 只把结果卡 display 写成 none、统计格归 0,但结果容器 innerHTML 仍留 1285 码元。
对照列表页文案
对照 tools.php 的 intro 共 25 条判定:一致 11、冲突 4、部分一致 10。最要紧的冲突是那句「8-4-4-4-12 共 32 个十六进制字符」:对齐批次 32 位,本页负 leg 只剩 31 位;另一处是 keywords 写着 GUID,而模板全文没有这个词。「v1 由时间戳和节点信息生成」部分一致:本页节点段是每次重摇的随机字节,没读网卡。版本位与变体位见旧文 UUID 生成器旧文,不复述其场景与概率;本页随机段接口与随机密码生成同族,熵源质量未实测;细节以 UUID 生成器工具页 为准。
FAQ
Q:这一页的 v1 时间戳段是不是标准的 60 位?
不是。三个字段只在这一个 32 位窗口取值,读到的位只有位 0 到 27 共 28 个、扣掉恒 0 的最低 4 位后只剩 24 个会变的位。
Q:为什么有时 uuid 里冒出一个减号、还少一位十六进制?
因为 timeLow 走 ToInt32、在近半数毫秒落在负 leg;substr(20,12) 又截掉节点段末 nibble,十六进制位只剩 31 个。