按位运算符简单地翻转整数中的所有位?

Posted

技术标签:

【中文标题】按位运算符简单地翻转整数中的所有位?【英文标题】:Bitwise operator for simply flipping all bits in an integer? 【发布时间】:2011-09-15 03:13:35 【问题描述】:

我必须翻转整数二进制表示中的所有位。给定:

10101

输出应该是

01010

当与整数一起使用时,完成此操作的位运算符是什么?例如,如果我正在编写像int flipBits(int n); 这样的方法,那么主体中会包含什么?我只需要翻转数字中已经存在的内容,而不是整数中的所有 32 位。

【问题讨论】:

OP 的意思是“我只需要翻转数字中已经存在的内容,而不是整数中的所有 32 位。”?如果数字是“000101”,他期望“111010”还是“000”,因为它后面跟着“010”,因为第一个从第3个LSB开始?无论哪种方式,它都与之前的陈述“我必须翻转所有位”不一致。 【参考方案1】:

~ 一元运算符是按位取反。如果您需要的位数少于 int 中的位数,那么您需要在事后使用 & 对其进行屏蔽。

【讨论】:

在 scala 中怎么样?当我做〜22时,我得到了完全错误的结果,我期望9,但我得到-23 刚刚实现scala默认不支持unassigned int;因此它会处理所有已签名的内容。我想我必须在执行~ 之后向左大移动 1 位才能得到 java 或人眼会得到的结果。【参考方案2】:

只需使用按位非运算符~

int flipBits(int n) 
    return ~n;

要使用 k 个最低有效位,请将其转换为正确的掩码。 (我假设你当然想要至少 1 位,这就是掩码从 1 开始的原因)

int flipBits(int n, int k) 
    int mask = 1;
    for (int i = 1; i < k; ++i)
        mask |= mask << 1;

    return ~n & mask;

根据Lưu Vĩnh Phúc 的建议,可以将掩码创建为(1 &lt;&lt; k) - 1,而不是使用循环。

int flipBits2(int n, int k) 
    int mask = (1 << k) - 1;
    return ~n & mask;

【讨论】:

不幸的是,这并没有给我预期的价值。 26 的按位反转应该是 11,但是在使用 ~ 时我得到了一些疯狂的值。有没有办法让它只使用整数中实际使用的位数? 在 java 中,ints 始终是 32 位(2 的补码),无论表示的数字大小如何 这是在我需要解决的问题中确立的。 顺便说一句,26 的按位反转不是 11,而是 5。26:11010,~26:00101 = 5。 要获得设置了 k 个低位的掩码,请使用 (1 &lt;&lt; k) - 1 而不是循环并设置每个位。【参考方案3】:

有很多方法可以使用操作来翻转所有位

x = ~x; // has been mentioned and the most obvious solution.
x = -x - 1; or x = -1 * (x + 1);
x ^= -1; or x = x ^ ~0;

【讨论】:

【参考方案4】:

好吧,因为到目前为止,只有一种解决方案可以给出“正确”的结果,而且那......真的不是一个很好的解决方案(使用字符串来计算前导零?这会在我的梦中困扰我;))

因此,我们在这里提供了一个不错的干净解决方案,该解决方案应该可以工作 - 虽然尚未对其进行彻底测试,但您明白了要点。确实,没有无符号类型的 java 对这类问题来说非常烦人,但它仍然应该非常有效(如果我可以这么说,比从数字中创建一个字符串要优雅得多)

private static int invert(int x) 
    if (x == 0) return 0; // edge case; otherwise returns -1 here
    int nlz = nlz(x);
    return ~x & (0xFFFFFFFF >>> nlz);


private static int nlz(int x) 
    // Replace with whatever number leading zero algorithm you want - I can think
    // of a whole list and this one here isn't that great (large immediates)
    if (x < 0) return 0;
    if (x == 0) return 32;
    int n = 0;
    if ((x & 0xFFFF0000) == 0) 
        n += 16;
        x <<= 16;
    
    if ((x & 0xFF000000) == 0) 
        n += 8;
        x <<= 8;
    
    if ((x & 0xF0000000) == 0) 
        n += 4;
        x <<= 4;
    
    if ((x & 0xC0000000) == 0) 
        n += 2;
        x <<= 2;
    
    if ((x & 0x80000000) == 0) 
        n++;
           
    return n;

【讨论】:

【参考方案5】:

更快更简单的解决方案:

/* inverts all bits of n, with a binary length of the return equal to the length of n
k is the number of bits in n, eg k=(int)Math.floor(Math.log(n)/Math.log(2))+1
if n is a BigInteger : k= n.bitLength();
*/
int flipBits2(int n, int k) 
    int mask = (1 << k) - 1;
    return n ^ mask;

【讨论】:

【参考方案6】:

我必须查看一些示例才能确定,但​​由于二进制补码运算,您可能会得到意想不到的值。如果数字有前导零(如在 26 的情况下),~ 运算符将翻转它们以使它们成为前导零 - 导致负数。

一种可能的解决方法是使用 Integer 类:

int flipBits(int n)
    String bitString = Integer.toBinaryString(n);
    int i = 0;

    while (bitString.charAt(i) != '1')
        i++;
    

    bitString = bitString.substring(i, bitString.length());

    for(i = 0; i < bitString.length(); i++)
        if (bitString.charAt(i) == '0')
            bitString.charAt(i) = '1';
        else
            bitString.charAt(i) = '0';
    

    int result = 0, factor = 1;

    for (int j = bitString.length()-1; j > -1; j--)
        result += factor * bitString.charAt(j);
        factor *= 2;
    

    return result;

我现在没有设置 java 环境来测试它,但这是一般的想法。基本上只需将数字转换为字符串,切断前导零,翻转位,然后将其转换回数字。 Integer 类甚至可能有某种方法将字符串解析为二进制数。我不知道问题是否需要这样解决,它可能不是最有效的方法,但它会产生正确的结果。

编辑:polygenlubricants 对this question 的回答也可能有帮助

【讨论】:

@Vuntic 就像我说的那样,这可能不是最好的方法,但它可以完成工作。这实际上只是您希望如何表示数据的问题。它也可以将数字左移直到前导零消失,翻转位,然后将其右移回来,但这最终不会变得更简单。当您必须使用 Java 等更适合 C 等低级语言的高级语言执行某些操作时,解决方案并不总是那么优雅。 @Ben 除了 Java 没有无符号类型(这里没有真正问题,但有点烦人)这一事实之外,C 或任何其他你能想到的语言的解决方案都是相同的允许有点玩弄 - 便宜的借口;)但确保多花几纳秒来执行这样的功能并不重要,而且解决方案很容易理解......本身还不错 - 它只是错过了良好数学的这种优雅解决方案恕我直言 @Voo,在 Java 中使用这样的字符串需要几十微秒。 ~ 需要数百皮秒。字符串在 Java 中的作用比在 C 中的作用更大。 @Peter 我以为我不会从字面上理解为“不会花费太长时间”。还考虑到现代 CPU 的频率仍然只有 @Voo,大多数现代 CPU 最好是 2-3.3 GHz,有些甚至高达 5.1 GHz。逐位反转通常是单个时钟周期指令。【参考方案7】:

我有另一种方法可以解决这个问题,

public static int complementIt(int c)
 return c ^ (int)(Math.pow(2, Math.ceil(Math.log(c)/Math.log(2))) -1);

它是使用 XOR 来获得补码位,为了补码我们需要将数据与 1 进行异或,例如:

101 异或 111 = 010

(111是'key',它是通过搜索数据的'n'平方根生成的)

如果你使用 ~(补码),结果将取决于它的变量类型,如果你使用 int,那么它将被处理为 32 位。

【讨论】:

你的方式效率极低。要获得 2^c,只需使用1 &lt;&lt; c,它比 log、ceil 和 pow 快数百倍。此外,它是完全精确的,而处理浮点数学可能会给您带来麻烦【参考方案8】:

由于我们只需要翻转整数所需的最小位(比如 50 是 110010,反转后变成 001101,即 13),我们可以一次将单个位从 LSB 反转到 MSB,并且继续向右移动位,并相应地应用 2 的幂。下面的代码完成了所需的工作:

int invertBits (int n) 
        int pow2=1, int bit=0;
            int newnum=0;
            while(n>0) 
              bit = (n & 1);
              if(bit==0)
                  newnum+= pow2;
              n=n>>1;
              pow2*=2;
          
          return newnum;
        

【讨论】:

【参考方案9】:
import java.math.BigInteger;
import java.util.Scanner;

public class CodeRace1 

    public static void main(String[] s) 
        long input;
        BigInteger num,bits = new BigInteger("4294967295");
        Scanner sc = new Scanner(System.in);
        input = sc.nextInt();
        sc.nextLine();
        while (input-- > 0) 
            num = new BigInteger(sc.nextLine().trim());
            System.out.println(num.xor(bits));
        
    

【讨论】:

虽然只有代码的答案对简单的上下文有效,但前提是它们是正确的;请记住,不推荐使用它们。始终尝试至少在最关键的部分提供对代码正在执行的什么以及如何为什么执行此操作的说明的代码。在这种特定情况下,还建议链接到BigInteger(String)Scanner.nextInt() 的官方文档。【参考方案10】:

来自openJDK的实现,Integer.reverse():

public static int More ...reverse(int i) 
    i = (i & 0x55555555) << 1 | (i >>> 1) & 0x55555555;
    i = (i & 0x33333333) << 2 | (i >>> 2) & 0x33333333;
    i = (i & 0x0f0f0f0f) << 4 | (i >>> 4) & 0x0f0f0f0f;
    i = (i << 24) | ((i & 0xff00) << 8) |
        ((i >>> 8) & 0xff00) | (i >>> 24);
    return i;

根据我在笔记本电脑上的实验,下面的实现速度更快:

public static int reverse2(int i) 
    i = (i & 0x55555555) << 1 | (i >>> 1) & 0x55555555;
    i = (i & 0x33333333) << 2 | (i >>> 2) & 0x33333333;
    i = (i & 0x0f0f0f0f) << 4 | (i >>> 4) & 0x0f0f0f0f;
    i = (i & 0x00ff00ff) << 8 | (i >>> 8) & 0x00ff00ff;
    i = (i & 0x0000ffff) << 16 | (i >>> 16) & 0x0000ffff;

    return i;

不确定其背后的原因是什么 - 因为它可能取决于如何将 java 代码解释为机器代码...

【讨论】:

【参考方案11】:

如果你只想翻转整数中“使用”的位,试试这个:

public int flipBits(int n) 
    int mask = (Integer.highestOneBit(n) << 1) - 1;
    return n ^ mask;

【讨论】:

【参考方案12】:
public static int findComplement(int num) 
    return (~num & (Integer.highestOneBit(num) - 1));

【讨论】:

这是一个合理的建议,但已经发布了类似的内容【参考方案13】:
int findComplement(int num) 
        int i = 0, ans = 0;
        while(num) 
            if(not (num & 1)) 
                ans += (1 << i);
            
            i += 1;
            num >>= 1;
        
        return ans;

【讨论】:

虽然此代码可能会为问题提供解决方案,但最好添加有关其工作原理/方式的上下文。这可以帮助未来的用户学习并将这些知识应用到他们自己的代码中。在解释代码时,您也可能会以赞成票的形式从用户那里获得积极的反馈。【参考方案14】:
          Binary 10101 == Decimal 21
  Flipped Binary 01010 == Decimal 10

一个班轮(在 javascript - 你可以转换成你最喜欢的编程语言)

10 == ~21 & (1 << (Math.floor(Math.log2(21))+1)) - 1

解释:

 10 == ~21 & mask

mask :用于过滤掉有效位计数之前的所有前导位(nBits - 见下文)

如何计算有效位数?

Math.floor(Math.log2(21))+1   => Returns how many significant bits are there (nBits)

例如:

0000000001 返回 1

0001000001 返回 7

0000010101 返回 5

(1 << nBits) - 1         => 1111111111.....nBits times = mask

【讨论】:

【参考方案15】:

可以通过简单的方法来完成,只需从值中减去数字即可 当所有位都等于 1 时获得。 例如: 号码:给定号码 值:所有位都设置在给定数字中的数字。 翻转数字 = 价值 – 数字。 例子 : 数字 = 23, 二进制形式:10111 翻转数字后数字将是:01000 值:11111 = 31

对于固定大小的整数,我们可以在 O(1) 时间内找到最高有效位。为了 下面的代码示例适用于 32 位整数。

int setBitNumber(int n)

n |= n>>1;
n |= n>>2;
n |= n>>4;
n |= n>>8;
n |= n>>16;
n = n + 1;
return (n >> 1);

【讨论】:

【参考方案16】:

单线解决方案

int flippingBits(int n) 
   return n ^ ((1 << 31) - 1);

【讨论】:

以上是关于按位运算符简单地翻转整数中的所有位?的主要内容,如果未能解决你的问题,请参考以下文章

完美实现按位 NOT (~) 运算符(翻转位)

GPU 上整数和按位运算的性能

位掩码 - C中的按位运算

位运算

如何使用按位运算获取整数的第N位?

位运算