水晶朗的"&-="运算符是什么?



我刚刚了解到Crystal中存在&-=运算符。它做什么?

下面是Mutex#try_lock:的一个例子

private def try_lock
i = 1000
while @state.swap(1) != 0
while @state.get != 0
Intrinsics.pause
i &-= 1
return false if i == 0
end
end
true
end

在尝试时,我看不出与熟悉的-=运算符有任何区别。例如,这两个片段产生相同的输出:

i = 1000
while i != 0
puts i
i -= 1
end
i = 1000
while i != 0
puts i
i &-= 1
end

谜题的第一块是认识到a &-= b只是a = a &- b的语法糖。或者更一般地,a op= ba = a op b的语法糖。语言参考资料在运算符中的"组合作业"下对此进行了详细说明。

现在我们需要问什么是&-,它与-有何不同?不幸的是,API文档在撰写本文时对此非常沉默。语言参考也不是很详细,但在上面的操作员页面上,我们可以找到:

-减法
&-包装减法

那么什么是包装减法?Crystal有固定大小的数字类型。因此,在这种情况下,它们可能会溢出或下溢。这是什么意思?让我们举一个例子:

# We have something to sell! Let's keep track of how many!
# It doesn't really make sense to have negative something left,
# so an unsigned integer ought to this.
items_left = 2u32
# Just made the first sell! Let's remember
items_left -= 1
# People seem to actually like this
items_left -= 1
# I could do this all day!
items_left -= 1 # => Unhandled exception: Arithmetic overflow (OverflowError)
# Oh no what happend?

因此,程序试图低于0,而UInt32类型无法表示。它下溢了。如果Crystal不做这个检查,CPU会很高兴地绕过整数类型,我们会在items_left(UInt32::MAX(中得到4294967295

但有时,在低级别代码中,这种行为正是我们想要的。例如,如果我们正在统计一些统计数据,比如发送的数据包,如果计数器溢出或下溢,我们不希望程序在这种情况下失败,那么重新包装是可以的。或者,我们有一些性能敏感的代码,并确保它运行正确,永远不会溢出,所以我们不想在检查操作是否刚刚溢出时支付额外的CPU周期。

对于这些情况,有前缀为&的数学运算符。它们只是在不进行任何溢出检查的情况下执行操作。如果我们在上面的例子中使用&-而不是-,那么现在items_left中就会有4294967295

一般情况下,您知道是否需要包装操作员或从包装操作员中受益。如果有疑问,就假装它们不存在。

最新更新