math.random()生成[0,1)更安全,因其无边界误差、精度可控;整数映射易致权重失真与并发碰撞;概率归一化应统一用weight/totalweight,扇形角度须转弧度并从0起始,文字定位用中点角,随机数需避免重复实例化。

为什么 Math.random() 生成的 [0,1) 区间比 [1,100] 更安全?
因为浮点误差和边界处理更可控——Math.random() 永远不会返回 1,但 Math.floor(Math.random() * 100) + 1 会稳定产出 [1,100] 整数,看似方便,实则隐藏两个坑:一是权重归一化时若用整数区间做除法(比如 weight / 100),一旦总权重不是 100 就直接失真;二是多人并发抽奖时,整数随机数碰撞概率略高(尤其低基数场景)。
实操建议:
- 始终用
Math.random()原生输出,不转整数再映射 - 权重归一化统一走
weight / totalWeight,保留小数精度 - 判定逻辑写成
if (rand >= cumSum[i-1] && rand ,左闭右开,避免边界重叠
如何用 reduce + map 构建累积概率数组?
这是前端实现轮盘抽奖最轻量、可读性最强的方式,避免手写 for 循环出错。关键不是“怎么算角度”,而是“怎么让每个奖品知道自己在哪个概率段里”。
示例代码(ES6):
const prizes = [
{ name: 'iPhone', weight: 5 },
{ name: 'AirPods', weight: 15 },
{ name: '谢谢参与', weight: 80 }
];
const total = prizes.reduce((sum, p) => sum + p.weight, 0);
const cumProbs = prizes.map((p, i, arr) =>
i === 0 ? p.weight / total : cumProbs[i-1] + p.weight / total
);常见错误现象:
- 忘记初始化
cumProbs[0],导致cumProbs[i-1]是undefined - 用
push在循环中操作数组,却没声明空数组,结果cumProbs变成undefined - 把
total写成硬编码 100,后续改权重就失效
drawArc 绘制扇形时 angle_start 和 angle_end 怎么对齐文字?
Canvas 绘图里,角度单位是弧度,不是度数;而人眼习惯看圆心角均分,所以必须把累积概率换算成 angle_start = cumSum[i-1] * Math.PI * 2,否则扇形会错位、文字压线、甚至整个转盘“少一块”。
实操要点:
- 起始角必须从 0 开始,不能用
Math.PI / 2等魔数强行旋转——那只是视觉欺骗,概率区间没变 - 文字定位要用扇形中点角:
midAngle = (angle_start + angle_end) / 2,再加偏移避免贴边 - 调用
ctx.save()/ctx.restore()包裹每次文字绘制,否则 rotate 状态会污染下一个扇形
抽奖结果不稳?检查随机数生成时机和实例复用
很多人把 new Random()(Java)或 Math.random()(JS)放在循环里、事件回调里反复调用,看似没问题,但实际会导致:iOS Safari 下同一毫秒内多次调用返回相同值;Node.js cluster 模式下多个进程共享种子;React 函数组件重渲染时重复执行逻辑。
正确做法:
- JS 环境统一用全局
Math.random(),不封装 new Random 类 - 服务端 Java/Go 等语言,用
ThreadLocalRandom.current()替代new Random() - 如果要做可重现抽奖(比如回放、审计),必须显式传入 seed,而不是依赖系统时间
最容易被忽略的一点:概率算法本身没问题,但前端把“抽中结果”和“动画旋转圈数”耦合了——用户看到停在某区域,其实是靠旋转角度反推的,而这个角度计算如果没对齐概率区间起点,就会出现“明明抽中 A 却停在 B 上”的错觉。










