论腾讯 TFC 抽奖程序作弊的可能性,以及代码中存在的 bug

腾讯 TFC 前端大会已经圆满结束,除了我的航班一直延误之外,还有 2 个遗憾:一个是 V8 定制版的 T 恤没有按时寄到,另一个就是没有抽中 iPhone 7。

但是抽奖代码绝对是会场的一大亮点,而且在场的所有参会者也有幸参与了对此次抽奖程序进行 code review,(认真脸)

当抽奖代码公布后,全场惊呼加爆笑。

主持人首先输入 Math.random 然后回车,控制台输出:

  1. > Math.random

  2. function random() { [native code] }

作用是为了确认 Math.random 是原生的代码,没有被重写过。

第一轮的抽奖程序是:

  1. ~~(0 + Math.random() * 51)

很多人开始在群里问:前面的两个波浪号是干什么的啊?

~ 是按位取反,这段代码使用了一个位运算的小技巧:通过 2 次按位取反来实现向下取整。

如果在抽奖之前听了我的 V8 topic 就会知道位运算要比 Math.floor 性能高出很多。但是细心的同学也会发现,这段代码存在 bug。

我在 V8 性能优化里面讲到过一个关键词——“截断”,在 ECMA 规范中对按位取反的定义:

产生式 UnaryExpression : ~ UnaryExpression 按照下面的过程执行 :

  1. 令 expr 为解释执行 UnaryExpression 的结果 .

  2. 令 oldValue 为 ToInt32(GetValue(expr)).

  3. 返回 oldValue 按位取反的结果。结果为 32 位有符号整数

不仅仅是 ~,所有位运算的结果都是 32 位有符号整数。如果我们运行 ~~(2**31) 就会得到一个负数 -2147483648。不过,即使全中国都来参加这次大会,程序也不会出 bug 的。

如果把这个运算和 Math.floor 对比,会发现两者的差异不仅仅是表示范围不同:

返回不大于 x 的且为数学整数的最大 ( 接近 +∞) 数字值。如果 x 已是整数,则返回 x

  1. 若 x 是 NaN, 返回结果是 NaN.

  2. 若 x 是 +0, 返回结果是 +0.

  3. 若 x 是 0, 返回结果是 0.

  4. 若 x 是 +∞, 返回结果是 +∞.

  5. 若 x 是 −∞, 返回结果是 −∞.

  6. 若 x 大于 0 但小于 1, 返回结果是 +0.

~ 的结果是 int32 的有符号整数,所以肯定不可能是 NaN 和无穷,因此 1、4、5 两者不同。

根据定义, Math.floor 向 +∞ 取整,比如 Math.floor(-1.5) 的结果是 -2,因为 -2 是不大于 -1.5 的最小的整数。而 ~~(-1.5) 的结果是 1

因为 ~ 的结果是整数,所以只有一个 0。(PS:正零和负零是浮点数里面的概念)。在 javascript 中 +0 完全等于 -0,那么怎么分区两者呢?

  1. > 0 == -0

  2. true

  3. > 0 === -0

  4. true

我们可以比较他们的倒数,因为 +0 的倒数是 +∞, 0 的倒数是 −∞

  1. > 1/0

  2. Infinity

  3. > 1/-0

  4. -Infinity

  5. > 1/0 == 1/-0

  6. false

  7. > 1/0 === 1/-0

  8. false

虽然如何,我们在特定的场景依然推荐使用 ~~ 运算来向下取整。首先位运算本身就会比函数调用快很多,在加之规范对 floor 的各种特殊值处理也导致了性能损失。我之前在 JavaScript 函数式编程的性能问题 中也分析过,直接使用 for 循环要比数组的 forEach 快 20 倍以上,也是类似的原因。

作为抽奖程序我们最关注的就是能不能作弊。抽奖之前的操作:

  1. > Math.random

  2. function random() { [native code] }

也是为了向观众说明,这段代码没有作弊。

果真如此吗?

如果我们重写了 random 函数,那么这段代码就会把我们的函数打印出来:

  1. Math.random

  2. () => '中奖用户是 jjc'

这样一下子就暴露了。那么我们有没有办法既重写了 random 函数,又不让用户知道我们重写了这个函数。答案是肯定的。我们可以继续重写 Math.random.toString 方法。

我们先运行:

  1. Math.random = () => '中奖用户是 jjc'

  2. Math.random.toString = () => 'function random() { [native code] }'

然后当我们在控制台输入以下语句时就会神不知鬼不觉了:

  1. > Math.random

  2. function random() { [native code] }

  3. > Math.random()

  4. "中奖用户是 jjc"

如果我以后再参加抽奖互动时突然获得了特等奖,那纯属巧合。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值