我已阅读 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
?
答案 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,这是0x55555555
和0x55555556
之间的数字。由于很快就会明显的原因,我们在这里使用高估,即舍入结果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 = 0101
,0xA = 1010
,0x0 = 0000
。因此,商的小数部分的两个最高有效位恰好对应于期望的模数值。由于我们正在处理32位操作数,因此我们可以通过右移30位然后使用0x3
掩码来提取这两位,以隔离两位。我认为在Java中需要屏蔽,因为32位整数总是被签名。对于C / C ++中的uint32_t
个操作数,仅移位就足够了。
我们现在明白为什么选择0x55555555
作为1/3的代表不起作用。商的小数部分将变为0xFFFFFFF*
,并且由于二进制的0xF = 1111
,模数计算将传递3的错误结果。
请注意,随着i
幅度的增加,来自1/3的不精确表示的累积误差会影响小数部分的越来越多的位。实际上,详尽的测试表明该方法仅适用于i < 0x60000000
:超出该限制,错误会超过代表我们结果的最重要的分数位。