1. Overview
1.概述
Sometimes we need to test whether a binary digit in a number is set or not. This may be because we’re using numbers as a set of flags, where each digit represents a particular boolean value.
有时我们需要测试一个数字中的二进制数字是否被设置。这可能是因为我们将数字作为一组标志,每个数字代表一个特定的布尔值。
In this tutorial, we’ll explore different ways to get a bit at a specific position from integral values, such as byte, short, char, int, and long.
在本教程中,我们将探讨从积分值中获取特定位置的位的不同方法,如byte、short、char、int和long。
2. Testing a Specific Bit
2.测试一个特定的位
One of the most common situations is that we want to test a specific bit of an integral value with a bitmask.
最常见的一种情况是,我们想用bitmask来测试一个积分值的特定位。
For example, let’s check whether the third bit is set in a byte value:
例如,让我们检查一个字节值中的第三位是否被设置。
byte val1 = 0b0110_0100;
byte mask = 0b0000_0100;
boolean isSet1 = (val1 & mask) > 0;
assertTrue(isSet1);
Here the binary number 01100100 is tested to see if the third bit – 00000100 is set by using bitwise AND. The result is greater than zero, so it is. We can also test if it’s not set:
这里通过使用bitwise AND测试二进制数01100100的第三位-00000100是否被设置。结果是大于零,所以是。我们也可以测试它是否没有被设置。
byte val2 = 0b0110_0010;
boolean isSet2 = (val2 & mask) > 0;
assertFalse(isSet2);
This example is based on the byte numeric type, and we can easily extend it to short, char, int, and long values.
这个例子是基于byte数字类型,我们可以很容易地将其扩展到short、char、int和long值。
In this solution, we’ve hard-coded the bitmask. What if we wanted to generalize the solution to check any bit in our number?
在这个解决方案中,我们对比特掩码进行了硬编码。 如果我们想把这个方案推广到检查我们数字中的任何一个位,会怎么样呢?
3. Using Shift Operator
3.使用移位操作符
Before starting, let’s first define the index range of the bit positions in a 32-bit int. The leftmost bit has an index of 31, and the rightmost bit has an index of 0. This is because our numbers run from the most significant to the least significant digits. For example, if we used 64-bit long numbers, the leftmost bit would be 63.
在开始之前,让我们首先定义一下32位int中的位的索引范围。最左边的位的索引是31,最右边的位的索引是0。这是因为我们的数字是从最重要的数字到最不重要的数字。例如,如果我们使用64位长数,最左边的位将是63。
3.1. Left Shift a Mask
3.1.左移一个掩码
We can generate a bitmask by taking the value 1 and moving it to the correct position by using the left shift operator:
我们可以通过取值1并使用左移运算符将其移动到正确的位置来生成一个比特掩码。
int val = 0b0110_0100;
int pos = 2;
int mask = 1 << pos;
boolean isSet = (val & mask) > 0;
assertTrue(isSet);
Here we have set pos to 2, though it could be any valid bit position in our number. Then, we use the left shift operator (<<) to generate our bitmask. Finally, we do a bitwise AND (&) operation between the val and the mask.
这里我们将pos设置为2,尽管它可以是我们数字中的任何有效位。然后,我们使用左移运算符(<<)来生成我们的比特掩码。最后,我们在val和mask之间做一个位和(&)操作。
If the result is greater than zero, it means the target bit is set.
如果结果大于零,意味着目标位被设置。
3.2. Left Shift the Value
3.2.将数值左移
In addition, there’s another way of solving this problem.
此外,还有一个解决这个问题的方法。
Instead of constructing a bitmask, we can use the left shift operator on the value we’re testing. Rather than filter the value with a bitmask, we can shift its contents so that the interesting bit is in the leftmost position.
我们可以在我们要测试的值上使用左移运算符,而不是构建一个比特掩码。与其用比特掩码来过滤这个值,我们不如将它的内容移到最左边的位置,这样有趣的位就在最左边。
Then all we need to do is check if the leftmost bit is set. As a signed integer is represented as two’s complement, we can test whether the leading digit is a one by testing if the resulting bit shifted number is negative.
那么我们所要做的就是检查最左边的位是否被设置。由于有符号的整数被表示为two’s complement,我们可以通过测试产生的移位数是否为负数来测试前导位是否为1。
int val = 0b0110_0100;
int pos = 2;
boolean isSet = ((val << (31 - pos)) < 0);
assertTrue(isSet);
In the above, the pos is 2, and the leftmost position is 31, so we use 31 to subtract pos, which equals 29. Then, we left-shift the original value 29-bit positions and get a new value. In this new value, the interesting bit is at the leftmost position. Finally, we check whether the new value is less than zero or not.
在上面,pos是2,而最左边的位置是31,所以我们用31减去pos,等于29。然后,我们将原值左移29位,得到一个新值。在这个新值中,有趣的位在最左边的位置。最后,我们检查新值是否小于零。
3.3. Right Shift the Value
3.3.右移值
Similarly, we can use the right shift operator to test a bit of an integral value. After moving the target bit of an integral value to the rightmost position and using a bitmask of 1, we can check if the result equals one:
同样地,我们可以使用右移操作符来测试一个积分值的位。在将一个积分值的目标位移到最右边的位置后,使用一个1,的比特掩码,我们可以检查结果是否等于1。
int val = 0b0110_0100;
int pos = 2;
boolean isSet = ((val >> pos) & 1) == 1;
assertTrue(isSet);
4. Optimising the Bitwise Solution
4.优化Bitwise解决方案
In cases where we might be performing these calculations a lot, we may wish to optimize our solution to use the least number of CPU instructions.
在我们可能经常进行这些计算的情况下,我们可能希望优化我们的解决方案,以使用最少的CPU指令。
Let’s look at a rewrite of the left shift solution, which might help us achieve this. It’s based on the assumption that bitwise operations are usually faster than arithmetic operations:
让我们来看看左移解决方案的重写,这可能有助于我们实现这一目标。它是基于这样的假设:位操作通常比算术操作快。
boolean isSet = ((val << (~pos & 31)) < 0);
We should note that the core idea has not changed. Just the writing of the code is subtlely different: we use (~pos & 31) to substitute the previous (31-pos) expression.
我们应该注意到,核心思想并没有改变。只是代码的写法有细微的不同:我们用(~pos & 31)来替代之前的(31-pos)表达式。
Why do these two expressions have the same effects? We can deduct this process:
为什么这两个表达方式有相同的效果?我们可以演绎这个过程。
(31 - pos) = (31 - pos) & 31
= (31 + (-pos)) & 31
= (31 & 31) + ((-pos) & 31)
= (31 & 31) + ((~pos + 1) & 31)
= (31 & 31) + (~pos & 31) + (1 & 31)
= ((31 + 1) & 31) + (~pos & 31)
= (32 & 31) + (~pos & 31)
= 0 + (~pos & 31)
= (~pos & 31)
At the beginning of this section, we mentioned the leftmost position is 31 and the rightmost position is 0, so (31 – pos) should be a positive number or zero. If we do a bitwise AND (&) operation between (31 – pos) and 31, the result remains the same. Then, we do it step by step. Finally, we get the (~pos & 31) expression.
在本节开头,我们提到最左边的位置是31,最右边的位置是0,所以(31 – pos)应该是一个正数或0。如果我们在(31 – pos)和31之间做一个位数和(&)操作,结果仍然是一样的。然后,我们一步一步地做。最后,我们得到(~pos & 31)表达式。
In this process, one more thing needs explanation: how does the (-pos) transform into (~pos + 1)? To get the two’s complement negative notation of an integer, we can do a bitwise COMPLEMENT (~) operation, then add one to the result.
在这个过程中,还有一件事需要解释:(-pos)/em>如何转化为(~pos + 1)/em>?为了得到整数的二补负数符号,我们可以做一个bitwise COMPLEMENT(~)操作,然后在结果中加1。
One step further, we can make the code a little more concise:
再往前走一步,我们可以让代码更简洁一点。
boolean isSet = ((val << ~pos) < 0);
In the above, we have left out bitwise AND (&) and 31. That’s because the JVM will do the job for us. An int value has 32-bit, and the JVM ensures that its valid shift range should be between 0 and 31. Similarly, a long value has 64-bit, and the JVM also makes sure its valid shift range should be between 0 and 63.
在上面,我们省略了位数和(&)和31。这是因为JVM将为我们做这项工作。一个int值有32位,JVM确保其有效移位范围应在0和31之间。同样,一个long值有64位,JVM也会确保其有效移位范围在0到63之间。
5. Using BigInteger
5.使用BigInteger
While the above binary math is the most computationally efficient for the built-in numeric types, we may need to check bits on numbers with more than 64 bits or may wish to have easier-to-read code.
虽然上述二进制数学对内置的数字类型来说计算效率最高,但我们可能需要对超过64位的数字进行检查,或者希望有更容易阅读的代码。
The BigInteger class can solve both these problems. It supports very large numbers with huge numbers of bits and provides a testBit method too:
BigInteger类可以解决这两个问题。它支持具有巨大比特数的非常大的数字,并且也提供了一个testBit方法。
int val = 0b0110_0100;
int pos = 2;
boolean isSet = BigInteger.valueOf(val).testBit(pos);
assertTrue(isSet);
6. Conclusion
6.结语
In this tutorial, we took a look at some common approaches to getting a bit at a certain position from integral values.
在本教程中,我们看了一些常见的方法,从积分值中得到某个位置的位。
As usual, the source code for this tutorial can be found over on GitHub.
像往常一样,本教程的源代码可以在GitHub上找到超过。