抽签、转盘、抽奖背后都是同一个问题:“随机”到底是什么。日常我们说”随机”指”我没法预测”,但计算机里”随机”细分成至少四个层次——每个层次决定了你的工具适合用什么场景。这篇用决策转盘做切入,把 Math.random、加权抽奖、动画与公平性、防作弊都讲清楚。
四种”随机”
| 类型 | 例子 | 可预测性 | 速度 |
|---|---|---|---|
| 真随机(TRNG) | 放射性衰变、热噪声 | 物理上不可预测 | 极慢,专用硬件 |
| 操作系统熵池 | /dev/urandom、Windows CryptoAPI | 实际不可预测 | 快,OS 内核服务 |
| CSPRNG | crypto.getRandomValues | 密码学安全 | 30M ops/s |
| PRNG | Math.random | 算法可推测 | 200M ops/s |
Math.random 是 PRNG——给定算法和种子,结果完全可复现。V8(Chrome / Node)用 xorshift128+,Safari 用 WebKit 的 Mersenne Twister 变体,Firefox 用 xorshift128+。所有现代浏览器周期都 ≥ 2^128,分布均匀性早就过统计学检验。
对决策转盘、班会抽签、谁请奶茶——PRNG 完全够用。担心”不够随”是想多了。真正会出问题的是下面几点。
加权抽奖:累计区间法
权重 5:3:2 不是把扇区切成 5:3:2 就完事——还要保证随机数落到对应区间。累计区间法是标准做法:
权重: A=5, B=3, C=2
累计: [5, 8, 10] 总和 = 10
r = Math.random() * 10
r ∈ [0, 5) → A
r ∈ [5, 8) → B
r ∈ [8, 10) → C
代码 6 行:
function weightedPick(items, weights) {
const total = weights.reduce((s, w) => s + w, 0);
let r = Math.random() * total;
for (let i = 0; i < items.length; i++) {
r -= weights[i];
if (r < 0) return items[i];
}
}
别用这些错误写法:
- ❌
Array(weight).fill(name)展开成大数组——权重 100 万时数组炸内存 - ❌ 对每项分别判
Math.random() < weight/total——会出现全不中或多个中 - ❌ 浮点累加 360°——
0.1 + 0.2 ≠ 0.3的经典 bug,最后一段可能少 0.1°
权重多到上万选项时,把累计区间存成有序数组,二分查找把抽奖从 O(n) 降到 O(log n)。再多用 Vose’s Alias Method——预处理后 O(1) 抽样,是大型抽奖系统的标配。
动画结果:先定还是后定
转盘动画有两种实现哲学:
方案 A:动画先转,停下来看结果
const finalAngle = startAngle + 1080 + Math.random() * 360;
animateTo(finalAngle);
const result = whichSectorAt(finalAngle);
问题:(1) 指针可能正好压在两扇区交界——视觉争议;(2) 浮点 + 动画缓动函数累计误差,“扇区面积比例”和”实际中签概率”不严格相等;(3) 加权场景实现复杂——要让最终角度”恰好”按权重分布。
方案 B:结果先定,反推角度
const result = weightedPick(items, weights); // 先抽出来
const sectorMid = sectorMidAngle(result); // 该扇区中线
const offset = (Math.random() - 0.5) * 0.7 * sectorWidth; // ±70% 随机偏移
const finalAngle = startAngle + 1080 + sectorMid + offset;
animateTo(finalAngle);
优势:(1) 结果由严格的加权抽样决定,扇区面积只是视觉演绎;(2) 永远不会压线;(3) 加权和动画解耦,加权算法换成什么都不影响动画。
所有正经抽奖动画都是方案 B——直播间宝箱、游戏抽卡、链上抽奖动画都是”结果先定,动画后演”。本工具用的也是方案 B。
视觉公平 ≠ 概率公平
人类大脑对随机分布有强烈偏见,这点决定了”看起来公平”和”实际公平”经常打架:
- 真随机会聚集——丢硬币 100 次出现”连续 7 次正面”是正常的(概率 ~50%),但人会怀疑被作弊
- 真随机会缺失——10 次抽奖某选项一次没出现是正常的,但用户会投诉”算法不准”
- Spotify 案例——早期纯随机播放被骂”老放同一首”,后来改成反聚集调度(避免最近放过的、分散同艺人)才平息
做转盘要不要降低连续重复率取决于场景:
| 场景 | 推荐 | 原因 |
|---|---|---|
| 决策(今天吃什么) | 真随机 | 连续抽到火锅就吃火锅,简单 |
| 轮班(谁洗碗) | 真随机 + “上次抽中本次权重 -1” | 体感公平,避免一周三次同一人 |
| 抽奖(中奖名单) | 真随机 + 公开算法 | 不能改算法,但要公开透明 |
| 游戏伪随机(暴击) | 保底机制 | 玩家心理预期,连续不暴击改为下次必暴击 |
抽奖防作弊:可审计三件套
本工具是单机娱乐场景,不需要防作弊。但如果你做正经抽奖(公司年会、几千元礼物、社群活动),公平抽奖必须满足三件:
- 可复现——公开种子(seed),任何人用同种子能复算出同一结果
- 不可预测——种子在抽奖前不可知;最常见做法是用未来某个区块哈希做种子
- 可审计——公开候选名单、权重、算法源码、最终结果,第三方能从头算一遍
典型方案对照:
| 方案 | 可复现 | 不可预测 | 可审计 | 适合 |
|---|---|---|---|---|
| 浏览器 Math.random | ❌ | ❌ | ❌ | 单机娱乐 |
| 服务端 CSPRNG + 公开 seed | ✅ | ⚠️ 信任服务端 | ⚠️ | 中等抽奖 |
| 公示规则 + 公证处 | ✅ | ✅ | ✅ | 大型彩票 |
| 链上 VRF(Chainlink) | ✅ | ✅ | ✅ | Web3 抽奖 |
| 未来区块哈希 | ✅ | ✅ | ✅ | 加密社群抽奖 |
链上 randomness 是黄金标准——所有数据上链不可改、可全网验证。本工具不参与,只负责”我和朋友决定吃什么”这层场景。
CSPRNG vs Math.random:什么时候必须升级
Math.random 够用的场景:动画、游戏 AI、洗牌玩单机斗地主、决策抽签、转盘抽奖(娱乐)。
必须用 crypto.getRandomValues 的场景:
const buf = new Uint32Array(1);
crypto.getRandomValues(buf);
const random32 = buf[0]; // 0 ~ 2^32-1
- UUID v4(标准要求 122 bit 密码学随机)
- 任何 token、session ID、密码重置链接
- 加密 key、IV、nonce
- 抽奖中的”种子”(防止用户反推下次结果)
速度对比:Math.random 约 200M ops/s,crypto.getRandomValues 约 30M ops/s——前者快 6-7 倍。但加密相关场景永远不能省这点性能。
洗牌别用 sort 黑魔法
经常看到这种洗牌写法:
// ❌ 错误!分布不均匀
arr.sort(() => Math.random() - 0.5);
为什么错:JS 的 sort 比较函数必须满足传递性(a<b 且 b<c → a<c),但 Math.random() - 0.5 完全随机,违反传递性。V8 / Firefox / Safari 三家排序实现不同(TimSort / merge sort),统计上前面位置的元素出现概率被低估或高估。Mike Bostock 写过著名的可视化对比。
Fisher-Yates 才对:
function shuffle(arr) {
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
O(n) 复杂度,证明严格均匀分布。本工具不做洗牌(决策转盘是抽样,不是排列),但同一族算法。
隐私与心理
本工具完全本地运行——选项、权重、历史记录全在你浏览器的 localStorage,不发任何网络请求。这反过来也意味着:抽奖结果只对自己可信——朋友看你转盘,他没法验证你点 GO 之前有没有调过权重。所以本工具适合”我自己用”或”在场所有人都看着屏幕”的场景,不适合远程抽奖。
要做”在场所有人都信”的抽奖:(1) 用本工具做演示动画;(2) 但真实结果用扔骰子、抓阄、提前公布的 seed 等所有人能验证的方式产生。技术解决不了信任问题,仪式感和透明度才能。
心法
- PRNG 对生活决策完全够用——别在”够不够随”上焦虑
- 加权抽奖用累计区间法——别用数组展开或浮点累加
- 动画结果先定后演——避免压线和精度争议
- 真公平要可审计——公开种子、算法、结果三件套
- 抽奖防作弊只在金额或情感重要时才必要——日常娱乐不必上链
打开 做个决定,输入”今天吃什么”的几个选项,按 GO 看一次——你看到的是 6 行 weighted pick + 反推角度动画的合奏,背后的随机性思考其实比大多数人想的复杂得多。