用二元运算检查除以3?

时间:2015-08-12 06:51:30

标签: java c# bit-manipulation bitwise-operators

我已阅读 this interesting answer 关于" 检查一个数字是否可以被3整除"

虽然答案是Java,但它似乎也适用于其他语言。

显然我们可以这样做:

boolean canBeDevidedBy3 = (i % 3) == 0;

但有趣的部分是另一个计算:

boolean canBeDevidedBy3 = ((int) (i * 0x55555556L >> 30) & 3) == 0;

为简单起见:

0x55555556L = "1010101010101010101010101010110"

的Nb

还有另一种检查方法:

  

通过计算1,可以确定整数是否可以被3整除   奇数位位的位,将此数乘以2,加上数字   在偶数位的1位,将它们添加到结果中并检查是否   结果可以被3整除

例如:

93 10 (可被3整除)
01011101 <子> 2

奇数位置有2位,偶数位置有4位(基于2数字位置的位置为零)

2*1 + 4 = 6可以被3整除。

起初我认为这两种方法是相关的,但我没有找到。

问题

如何

  boolean canBeDevidedBy3 = ((int) (i * 0x55555556L >> 30) & 3) == 0;

- 实际确定是否i%3==0

2 个答案:

答案 0 :(得分:4)

每当您向数字添加3时,您所做的就是添加二进制文件11。无论数字的原始值是什么,这将保持在奇数位置的1位数的两倍,加上偶数位置的1位数的不变量也将被3整除。

你可以通过这种方式看到。让我们调用上面的表达式c的值。您将1添加到奇数位置,将1添加到偶数位置。当您将1添加到偶数位置时,您已添加1的位已设置或未设置。如果未设置,则您将c的值增加1,因为您已在奇数位置添加了新的1。如果之前已设置,则会翻转该位,但在偶数位置(从进位)添加1。这意味着您最初将c减少1,但现在当您将1添加到偶数位置时,您将c增加2,因此整体而言c增加了{{} 1}} by 2。

当然,这个携带位也可能会被添加到已设置的位,在这种情况下,我们需要检查此部分是否仍然增加c 2:你将删除1处于偶数位置(将c减少2),然后在奇数位置添加1(将c增加1),这意味着我们&#39}实际上,c减少了1.这与将c增加2相同,但是,如果我们正在以模3运算。

更正式的版本可以通过归纳来证明。

答案 1 :(得分:2)

这两种方法似乎没有关系。当使用数字基b-1(在十进制算术中称为"casting out nines")时,逐位方法似乎与用于有效计算模b的某些方法有关。

基于乘法的方法直接基于除法的定义,当通过与倒数相乘来完成时。让/表示数学除法,我们有

int_quot = (int)(i / 3)
frac_quot = i / 3 - int_quot = i / 3 - (int)(i / 3)
i % 3 = 3 * frac_quot = 3 * (i / 3 - (int)(i / 3))

数学商的小数部分直接转换为整数除法的余数:如果分数为0,则余数为0,如果分数为1/3,则余数为1,如果分数为2/3,则余数是2.这意味着我们只需要检查商的小数部分。

我们可以乘以1/3,而不是除以3。如果我们以32.32定点格式执行计算,则1/3对应于2 32 * 1/3,这是0x555555550x55555556之间的数字。由于很快就会明显的原因,我们在这里使用高估,即舍入结果0x555555556

当我们将0x55555556乘以i时,完整64位产品的最高32位将包含商(int)(i * 1/3) = (int)(i / 3)的整数部分。我们对这个不可分割的部分不感兴趣,所以我们既不计算也不存储它。产品的低32位是0 / 3,1 / 3,2 / 3的分数之一,但是由于我们的0x555555556的值略大于1/3而略有误差计算:

i = 1:  i * 0.55555556 = 0.555555556
i = 2:  i * 0.55555556 = 0.AAAAAAAAC
i = 3:  i * 0.55555556 = 1.000000002
i = 4:  i * 0.55555556 = 1.555555558
i = 5:  i * 0.55555556 = 1.AAAAAAAAE

如果我们检查二进制中三个可能分数值的最高有效位,我们会发现0x5 = 01010xA = 10100x0 = 0000。因此,商的小数部分的两个最高有效位恰好对应于期望的模数值。由于我们正在处理32位操作数,因此我们可以通过右移30位然后使用0x3掩码来提取这两位,以隔离两位。我认为在Java中需要屏蔽,因为32位整数总是被签名。对于C / C ++中的uint32_t个操作数,仅移位就足够了。

我们现在明白为什么选择0x55555555作为1/3的代表不起作用。商的小数部分将变为0xFFFFFFF*,并且由于二进制的0xF = 1111,模数计算将传递3的错误结果。

请注意,随着i幅度的增加,来自1/3的不精确表示的累积误差会影响小数部分的越来越多的位。实际上,详尽的测试表明该方法仅适用于i < 0x60000000:超出该限制,错误会超过代表我们结果的最重要的分数位。