数字的位提取

3
尝试从数字中检索一些位,例如下面的字节11中标记为00001011的位。
(byte) 11 >> 1 << 6 >> 5

但为什么结果是10而不是2?
@EDIT
为了创建下面的方法,来自@Yassin Hajaj的解决方案似乎更可行。
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

或者更加通用,带有@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

你为什么认为它应该是“2”? - Yassin Hajaj
Java 中没有按位与运算符吗?0b11 & 0x06 - infixed
@infixed 是的! - Michael
4个回答

6

您没有指明初始数字为二进制系统。利用二进制文字,您可以通过在数字前加上0b来修复此问题。代码然后输出所需的2

byte b = (byte) 0b11 >> 1 << 6 >> 5;
System.out.println(b); // 2

从性能方面来看,您的解决方案与@Yassin Hajaj下面的哪一个更有效率? - sof
这行代码是不可行的。<< 6 的目的是通过将字节向左滚动来移除上5位,但<<会强制类型转换为int,所以上5位 没有 被清除。 --- 此外,问题要求使用十进制的 11 ,而不是二进制的 0b11 作为起始值,因此初始值显示为 00001011 。--- 目的是 xxxxxYZx >> 10xxxxxYZ<< 6YZ000000>> 500000YZ0 - Andreas
2
@sof 另一个回答做了完全不同的事情,恰好得出相同的答案。无论如何,所有这些解决方案都不需要时间,因为它们被编译为常量。您需要弄清楚正确的数学是什么。 - Louis Wasserman

3

另一种获取2的方法是在到达<< 6后强制将类型转换为byte,以仅保留数字的最后八位。

public static void main(String[] args) {
    byte b;
    b = (byte) (11 >> 1 << 6) >> 5;
    System.out.println(b); // 2
}

2
小心符号扩展。你应该使用>>>。此外,将11强制转换为byte是没有意义的,因为>>会立即将其强制转换回int - Andreas
实际上,>>> 也不起作用,因为它会被强制转换为 int - Andreas
使用0xFF进行AND运算(& 0xff)可能比强制转换为“byte”类型更好,以隔离8位。 - infixed

2

所以你有一个十进制数11 (0b1011 或 0x0B),你想要获取第二位和第四位的值?

只需使用这两个位进行按位与操作(0x04 + 0x02 = 0x06)

x = 11 & 0x06;

不需要进行移位操作


如果要创建一个像 getBits(number, fromMth, toNth) 这样的方法,仍然需要使用 shift,否则你是正确的。 - sof
从前,当ALU很弱小且指令集稀少时,一堆移位操作被认为比简单的AND操作更昂贵。即使是制作通用函数,预先计算掩码也会加快速度。特别是如果结果位要保持在它们原来的位置上。一个包含8个不同掩码的表格。取M的掩码与N的掩码进行异或,然后将其与您的输入进行AND运算。 - infixed

2

TL;DR 使用b&6,例如(byte)(11&6)。请查看末尾的可工作的getBits()实现。


首先,将11强制转换为byte没有什么意义,因为>>运算符会将其强制转回一个int值。

为了向您展示为什么您的代码不起作用,这里有一个显示所有中间步骤的程序:

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;
}

输出:

  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

您的高位未被清除,因为它正在处理 int 值。如果您将所有内容更改为 byte,则会得到:

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

问题在于使用>>时会出现符号扩展。即使切换到>>>也不起作用,因为>>>在移位之前仍将其强制转换为带符号的int

要消除符号扩展,必须使用b & 0xFFbyte转换为int,因为&会将b强制转换为带符号的int,然后按位与运算符将删除所有这些位。

当然,如果您无论如何都要使用按位与运算符,请使用它来获得所需的结果,即b & 0b00000110(或b & 6)。


由于上述原因,getBits()方法无法正常工作。

解决方案仍然是使用按位与运算符,但是从提供的fromto值构造位掩码。

这里的诀窍是使用(1<<x)-1创建x位的掩码,例如:5 -> 0b00011111。因此,如果您想从2到4(包括2和4)构建0x00011111(5位)和0x00000011(2位),然后将它们异或以获取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

对于其他原始类型,请重载该方法:
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;
}

非常好的写作,显然是一个被接受的答案,但需要更多时间来消化。非常感谢! - sof

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接