sof sof - 6 months ago 16
Java Question

Bits retrieval of a number

Tried to retrieve some bits of a number, e.g. the marked bits 00001

01
1 of the byte 11 below,

(byte) 11 >> 1 << 6 >> 5


but why is the result 10 instead of 2 ?

@EDIT

To make a method below, the solution from @Yassin Hajaj seems more feasible.

public byte getBits(byte b, int from, int to) { // from & to inclusive

return (byte) (b >> from << (8 - (to - from + 1))) >> (8 - to - 1);
}

getBits((byte) 11, 1, 2); // => 2


Or more universal with the hints from @Andreas,

public <T extends Number> long getBits(T n, int from, int to) {

return n.longValue() >>> from << (64 - (to - from + 1)) >>> (64 - to - 1);
}

getBits((byte) 11, 1, 2); // => 2

Answer

TL;DR Use b & 6, e.g. (byte)(11 & 6). See working getBits() implementation(s) at the end.


First of all, casting 11 to a byte is rather meaningless, because the >> operator will coerce it right back to an int value.

To show you why your code doesn't work, here is a program that displays all the intermediate steps:

public static void main(String[] args) {
    for (byte i = 0; i <= 16; i++) {
        int i1 = i >> 1;
        int i2 = i1 << 6;
        int i3 = i2 >> 5;
        System.out.printf("%3d %s -> %3d %s -> %3d %10s -> %3d %s%n", i, bin(i), i1, bin(i1), i2, bin(i2), i3, bin(i3));
    }
}
private static String bin(int value) {
    String s = Integer.toBinaryString(value);
    return "0000000".substring(Math.min(7, s.length() - 1)) + s;
}

Output:

  0 00000000 ->   0 00000000 ->   0   00000000 ->   0 00000000
  1 00000001 ->   0 00000000 ->   0   00000000 ->   0 00000000
  2 00000010 ->   1 00000001 ->  64   01000000 ->   2 00000010
  3 00000011 ->   1 00000001 ->  64   01000000 ->   2 00000010
  4 00000100 ->   2 00000010 -> 128   10000000 ->   4 00000100
  5 00000101 ->   2 00000010 -> 128   10000000 ->   4 00000100
  6 00000110 ->   3 00000011 -> 192   11000000 ->   6 00000110
  7 00000111 ->   3 00000011 -> 192   11000000 ->   6 00000110
  8 00001000 ->   4 00000100 -> 256  100000000 ->   8 00001000
  9 00001001 ->   4 00000100 -> 256  100000000 ->   8 00001000
 10 00001010 ->   5 00000101 -> 320  101000000 ->  10 00001010
 11 00001011 ->   5 00000101 -> 320  101000000 ->  10 00001010
 12 00001100 ->   6 00000110 -> 384  110000000 ->  12 00001100
 13 00001101 ->   6 00000110 -> 384  110000000 ->  12 00001100
 14 00001110 ->   7 00000111 -> 448  111000000 ->  14 00001110
 15 00001111 ->   7 00000111 -> 448  111000000 ->  14 00001110
 16 00010000 ->   8 00001000 -> 512 1000000000 ->  16 00010000

Your upper bits are not getting cleared, because it's operating on int values. If you change everything to byte, you get:

public static void main(String[] args) {
    for (byte i = 0; i <= 16; i++) {
        byte i1 = (byte)(i >> 1);
        byte i2 = (byte)(i1 << 6);
        byte i3 = (byte)(i2 >> 5);
        System.out.printf("%3d %s -> %3d %s -> %4d %s -> %3d %s%n", i, bin(i), i1, bin(i1), i2, bin(i2), i3, bin(i3));
    }
}
private static String bin(byte value) {
    String s = Integer.toBinaryString(value & 0xFF);
    return "0000000".substring(s.length() - 1) + s;
}
  0 00000000 ->   0 00000000 ->    0 00000000 ->   0 00000000
  1 00000001 ->   0 00000000 ->    0 00000000 ->   0 00000000
  2 00000010 ->   1 00000001 ->   64 01000000 ->   2 00000010
  3 00000011 ->   1 00000001 ->   64 01000000 ->   2 00000010
  4 00000100 ->   2 00000010 -> -128 10000000 ->  -4 11111100
  5 00000101 ->   2 00000010 -> -128 10000000 ->  -4 11111100
  6 00000110 ->   3 00000011 ->  -64 11000000 ->  -2 11111110
  7 00000111 ->   3 00000011 ->  -64 11000000 ->  -2 11111110
  8 00001000 ->   4 00000100 ->    0 00000000 ->   0 00000000
  9 00001001 ->   4 00000100 ->    0 00000000 ->   0 00000000
 10 00001010 ->   5 00000101 ->   64 01000000 ->   2 00000010
 11 00001011 ->   5 00000101 ->   64 01000000 ->   2 00000010
 12 00001100 ->   6 00000110 -> -128 10000000 ->  -4 11111100
 13 00001101 ->   6 00000110 -> -128 10000000 ->  -4 11111100
 14 00001110 ->   7 00000111 ->  -64 11000000 ->  -2 11111110
 15 00001111 ->   7 00000111 ->  -64 11000000 ->  -2 11111110
 16 00010000 ->   8 00001000 ->    0 00000000 ->   0 00000000

Here, the problem is the sign-extension you get from >>. Even switching to >>> won't work, because the >>> still coerces to int with sign-extension before the shift happens.

To get rid of sign-extension, you have to convert byte to int using b & 0xFF, because the & will coerce b to an int with sign-extension, then the bitwise AND operator will remove all those bits.

Of course, if you're going to use bitwise AND anyway, just use it to get the desired result, i.e. b & 0b00000110 (or b & 6).


For the same reason as described above, the getBits() method doesn't work.

Solution is to still use a bitwise AND operator, but construct the bit-mask from the supplied from and to values.

The trick here is the use (1 << x) - 1 to create a mask of x bits, e.g. 5 -> 0b00011111. So if you want from 2 to 4 inclusive, build 0x00011111 (5! bits) and 0x00000011 (2 bits), then XOR them to get 0x00011100.

public static byte getBits(byte b, int from, int to) {
    if (from < 0 || from > to || to > 7)
        throw new IllegalArgumentException();
    int mask = ((1 << (to + 1)) - 1) ^ ((1 << from) - 1);
    return (byte)(b & mask);
}
  0 00000000 ->   0 00000000
  1 00000001 ->   0 00000000
  2 00000010 ->   2 00000010
  3 00000011 ->   2 00000010
  4 00000100 ->   4 00000100
  5 00000101 ->   4 00000100
  6 00000110 ->   6 00000110
  7 00000111 ->   6 00000110
  8 00001000 ->   0 00000000
  9 00001001 ->   0 00000000
 10 00001010 ->   2 00000010
 11 00001011 ->   2 00000010
 12 00001100 ->   4 00000100
 13 00001101 ->   4 00000100
 14 00001110 ->   6 00000110
 15 00001111 ->   6 00000110
 16 00010000 ->   0 00000000

For other primitive types, overload the method:

public static byte getBits(byte value, int from, int to) {
    if (from < 0 || from > to || to > 7)
        throw new IllegalArgumentException();
    int mask = ((1 << (to + 1)) - 1) ^ ((1 << from) - 1);
    return (byte)(value & mask);
}
public static short getBits(short value, int from, int to) {
    if (from < 0 || from > to || to > 15)
        throw new IllegalArgumentException();
    int mask = ((1 << (to + 1)) - 1) ^ ((1 << from) - 1);
    return (short)(value & mask);
}
public static int getBits(int value, int from, int to) {
    if (from < 0 || from > to || to > 31)
        throw new IllegalArgumentException();
    int mask = ((1 << (to + 1)) - 1) ^ ((1 << from) - 1);
    return value & mask;
}
public static long getBits(long value, int from, int to) {
    if (from < 0 || from > to || to > 63)
        throw new IllegalArgumentException();
    long mask = ((1L << (to + 1)) - 1) ^ ((1L << from) - 1); // <-- notice the change to long and 1L
    return value & mask;
}
Comments