【问题标题】:Bitwise operation and usage按位运算和使用
【发布时间】:2018-02-04 04:11:03
【问题描述】:

考虑这段代码:

x = 1        # 0001
x << 2       # Shift left 2 bits: 0100
# Result: 4

x | 2        # Bitwise OR: 0011
# Result: 3

x & 1        # Bitwise AND: 0001
# Result: 1

我可以理解 Python(和其他语言)中的算术运算符,但我从来没有很好地理解“按位”运算符。在上面的示例中(来自 Python 书籍),我理解左移,但不理解其他两个。

另外,按位运算符实际用于什么?我会很感激一些例子。

【问题讨论】:

标签: python binary operators bit-manipulation


【解决方案1】:

位运算符是处理多位值的运算符,但从概念上讲是一次一位。

  • AND 仅当 两个 的输入都为 1 时为 1,否则为 0。
  • OR 如果其中一个或两个输入为 1,则为 1,否则为 0。
  • XOR 仅当 恰好一个其输入为 1 时为 1,否则为 0。
  • NOT 仅当其输入为 0 时为 1,否则为 0。

这些通常可以最好地显示为真值表。输入可能性在顶部和左侧,结果位是输入交叉处显示的四个(在 NOT 的情况下为两个,因为它只有一个输入)值之一。

AND | 0 1     OR | 0 1     XOR | 0 1    NOT | 0 1
----+-----    ---+----     ----+----    ----+----
 0  | 0 0      0 | 0 1       0 | 0 1        | 1 0
 1  | 0 1      1 | 1 1       1 | 1 0

一个例子是,如果你只想要一个整数的低 4 位,你将它与 15(二进制 1111)相加,所以:

    201: 1100 1001
AND  15: 0000 1111
------------------
 IS   9  0000 1001

在这种情况下,15 中的零位有效地充当过滤器,迫使结果中的位也为零。

此外,&gt;&gt; 和 &lt;&lt; 通常包含在按位运算符中,它们分别将一个值左右“移动”一定数量的位,丢弃在您要移位的末尾滚动的位朝向,并在另一端输入零位。

所以,例如:

1001 0101 >> 2 gives 0010 0101
1111 1111 << 4 gives 1111 0000

请注意,Python 中的左移是不寻常的,因为它没有使用固定宽度来丢弃位 - 虽然许多语言使用基于数据类型的固定宽度,但 Python 只是扩展宽度以适应额外的位。为了在 Python 中获得丢弃行为,您可以使用按位 and 进行左移,例如将 8 位值左移四位:

bits8 = (bits8 << 4) & 255

考虑到这一点,位运算符的另一个示例是,如果您有两个 4 位值要打包成一个 8 位值,则可以使用所有三个运算符(left-shift、and和or):

packed_val = ((val1 & 15) << 4) | (val2 & 15)
  • &amp; 15 操作将确保两个值都只有低 4 位。
  • &lt;&lt; 4 是左移 4 位,将val1 移动到 8 位值的前 4 位。
  • | 只是将这两者结合在一起。

如果val1 是 7 而val2 是 4:

                val1            val2
                ====            ====
 & 15 (and)   xxxx-0111       xxxx-0100  & 15
 << 4 (left)  0111-0000           |
                  |               |
                  +-------+-------+
                          |
| (or)                0111-0100

【讨论】:

    【解决方案2】:

    一种典型用法:

    |用于设置某位为1

    &amp;用于测试或清除某个位

    • 设置一个位(其中 n 是位数,0 是最低有效位):

      unsigned char a |= (1 &lt;&lt; n);

    • 清楚一点:

      unsigned char b &amp;= ~(1 &lt;&lt; n);

    • 稍微切换一下:

      unsigned char c ^= (1 &lt;&lt; n);

    • 测试一下:

      unsigned char e = d &amp; (1 &lt;&lt; n);

    以您的列表为例:

    x | 2用于将x的第1位设置为1

    x &amp; 1用于测试x的bit 0是1还是0

    【讨论】:

      【解决方案3】:

      位运算符实际用于什么?我会很感激一些例子。

      按位运算最常见的用途之一是解析十六进制颜色。

      例如,这是一个 Python 函数,它接受类似 #FF09BE 的字符串并返回其 Red、Green 和 Blue 值的元组。

      def hexToRgb(value):
          # Convert string to hexadecimal number (base 16)
          num = (int(value.lstrip("#"), 16))
      
          # Shift 16 bits to the right, and then binary AND to obtain 8 bits representing red
          r = ((num >> 16) & 0xFF)
      
          # Shift 8 bits to the right, and then binary AND to obtain 8 bits representing green
          g = ((num >> 8) & 0xFF)
      
          # Simply binary AND to obtain 8 bits representing blue
          b = (num & 0xFF)
          return (r, g, b)
      

      我知道有更有效的方法可以实现这一点,但我相信这是一个非常简洁的示例,说明了移位和按位布尔运算。

      【讨论】:

        【解决方案4】:

        我认为问题的第二部分:

        另外,按位运算符实际用于什么?我会很感激一些例子。

        仅部分解决。这是我在这件事上的两分钱。

        在处理大量应用程序时,编程语言中的位运算起着重要作用。几乎所有的低级计算都必须使用这种操作来完成。

        在所有需要在两个节点之间发送数据的应用中,例如:

        • 计算机网络;

        • 电信应用(手机、卫星通信等)。

        在较低级别的通信层中,数据通常以帧的形式发送。帧只是通过物理通道发送的字节串。这些帧通常包含实际数据以及一些其他字段(以字节编码),这些字段是所谓的标头的一部分。报头通常包含对与通信状态相关的一些信息(例如,带有标志(位))、帧计数器、纠正和错误检测代码等进行编码的字节。以获取帧中传输的数据,并构建帧来发送数据,你需要确定的按位运算。

        通常,在处理此类应用程序时,可以使用 API,因此您不必处理所有这些细节。例如,所有现代编程语言都提供用于套接字连接的库,因此您实际上不需要构建 TCP/IP 通信框架。但是想想那些为你编写这些 API 的好人,他们肯定必须处理框架构造;使用各种按位运算从低层到高层通信。

        作为一个具体的例子,假设有人给你一个文件,其中包含由电信硬件直接捕获的原始数据。在这种情况下,为了找到帧,您需要读取文件中的原始字节并尝试通过逐位扫描数据来找到某种同步字。识别同步字后,您需要获取实际帧,并在必要时SHIFT(这只是故事的开始)以获取正在传输的实际数据。

        另一个非常不同的低级应用程序系列是当您需要使用某些(某种古老的)端口(例如并行端口和串行端口)来控制硬件时。这些端口是通过设置一些字节来控制的,并且这些字节的每一位都具有特定的含义,就指令而言,对于该端口(例如参见http://en.wikipedia.org/wiki/Parallel_port)。如果您想构建与该硬件相关的软件,您将需要按位运算将您要执行的指令转换为端口可以理解的字节。

        例如,如果您有一些物理按钮连接到并行端口来控制其他设备,这是您可以在软件应用程序中找到的一行代码:

        read = ((read ^ 0x80) >> 4) & 0x0f; 
        

        希望这会有所帮助。

        【讨论】:

        【解决方案5】:

        我没有看到上面提到它,但你也会看到有些人使用左移和右移进行算术运算。左移 x 相当于乘以 2^x(只要不溢出),右移相当于除以 2^x。

        最近我看到人们使用 x > 1 来加倍和减半,尽管我不确定他们是否只是想变得聪明,或者是否真的比普通运算符有明显的优势.

        【讨论】:

        • 我不了解 python,但在 C 甚至更低级的汇编语言等低级语言中,按位移位的效率要高得多。要查看差异,您可以用 C 语言编写一个程序,以每种方式执行此操作,然后编译为汇编代码(或者如果您知道汇编语言,那么您已经知道这一点:))。查看指令数量的差异。
        • 我反对使用移位运算符的论点是,大多数现代编译器可能已经在优化算术运算,因此聪明之处充其量是没有实际意义的,或者最坏的情况是与编译器作斗争。我没有 C、编译器或 CPU 设计方面的专业知识,因此不认为我是正确的。 :)
        • 这应该更高。我不得不处理一些完全以这种方式使用按位运算符的代码,而这个答案帮助我弄清楚了。
        【解决方案6】:

        我希望这可以澄清这两个:

        x | 2
        
        0001 //x
        0010 //2
        
        0011 //result = 3
        

        x & 1
        
        0001 //x
        0001 //1
        
        0001 //result = 1
        

        【讨论】:

        • 哎呀...试图成为西方最快的枪....最终成为一个连二进制都不知道的白痴:(修复它。
        • x &amp; 1 并没有像x &amp; 2 那样说明效果。
        【解决方案7】:

        认为 0 为假,1 为真。然后按位 and(&) 和 or(|) 就像常规的 and and or 一样工作,只是它们一次执行值中的所有位。通常,如果您有 30 个可以设置的选项(例如窗口上的绘制样式),您会看到它们用于标志,您不想传递 30 个单独的布尔值来设置或取消设置每个布尔值,所以您使用 |将选项组合成一个值,然后使用 & 检查是否设置了每个选项。 OpenGL 大量使用这种标志传递方式。由于每个位都是一个单独的标志,因此您会获得基于二的幂的标志值(也就是只有一个位设置的数字)1(2^0) 2(2^1) 4(2^2) 8(2^3) 2 的幂告诉你如果标志打开,哪个位被设置。

        还要注意 2 = 10 所以 x|2 是 110(6) 而不是 111(7) 如果没有位重叠(在这种情况下是真的)|就像加法一样。

        【讨论】:

          【解决方案8】:

          套装

          可以使用数学运算组合集合。

          • 联合运算符| 将两个集合组合成一个新的集合,其中包含其中的项目。
          • 交集运算符&amp; 仅在两者中获取项目。
          • 差异运算符- 获取第一个集合中的项目,但不获取第二个集合中的项目。
          • 对称差分运算符 ^ 获取任一集合中的项目,但不能同时获取两者。

          自己试试吧:

          first = {1, 2, 3, 4, 5, 6}
          second = {4, 5, 6, 7, 8, 9}
          
          print(first | second)
          
          print(first & second)
          
          print(first - second)
          
          print(second - first)
          
          print(first ^ second)
          

          结果:

          {1, 2, 3, 4, 5, 6, 7, 8, 9}
          
          {4, 5, 6}
          
          {1, 2, 3}
          
          {8, 9, 7}
          
          {1, 2, 3, 7, 8, 9}
          

          【讨论】:

          • 这个答案与问题完全无关,似乎是从别处复制粘贴过来的。
          • 问题是“位运算符实际用于什么?”。这个答案提供了位运算符的鲜为人知但非常有用的用法。
          【解决方案9】:

          此示例将向您展示所有四个 2 位值的操作:

          10 | 12
          
          1010 #decimal 10
          1100 #decimal 12
          
          1110 #result = 14
          

          10 & 12
          
          1010 #decimal 10
          1100 #decimal 12
          
          1000 #result = 8
          

          这是一个使用示例:

          x = raw_input('Enter a number:')
          print 'x is %s.' % ('even', 'odd')[x&1]
          

          【讨论】:

            【解决方案10】:

            另一个常见的用例是操作/测试文件权限。请参阅 Python 统计模块:http://docs.python.org/library/stat.html。

            例如,要将文件的权限与所需的权限集进行比较,您可以执行以下操作:

            import os
            import stat
            
            #Get the actual mode of a file
            mode = os.stat('file.txt').st_mode
            
            #File should be a regular file, readable and writable by its owner
            #Each permission value has a single 'on' bit.  Use bitwise or to combine 
            #them.
            desired_mode = stat.S_IFREG|stat.S_IRUSR|stat.S_IWUSR
            
            #check for exact match:
            mode == desired_mode
            #check for at least one bit matching:
            bool(mode & desired_mode)
            #check for at least one bit 'on' in one, and not in the other:
            bool(mode ^ desired_mode)
            #check that all bits from desired_mode are set in mode, but I don't care about 
            # other bits.
            not bool((mode^desired_mode)&desired_mode)
            

            我将结果转换为布尔值,因为我只关心真假,但打印出每个值的 bin() 值将是一个值得练习的练习。

            【讨论】:

            • 上一个例子你错了。这里应该是这样的:not bool((mode ^ desired_mode) &amp; 0777)。或者(更容易理解):not (mode &amp; 0777) ^ desired_mode == 0。 AND 将只留下有趣的位,XOR 将检查所有需要的位都设置了什么。显式的== 0 比较比bool() 更有意义。
            • 我不认为这是特定于文件操作的。例如,在 PyQt 中,您对 setWindowFlags 执行类似的操作。示例:setWindowFlags(SplashScreen | WindowStaysOnTopHint)。我仍然觉得这很令人困惑,因为您将其设置为“打开”似乎是一个切换,因此在这种情况下“和”似乎更直观。
            【解决方案11】:

            整数的位表示通常在科学计算中用于表示真假信息数组,因为按位运算比遍历布尔数组要快得多。 (高级语言可能会使用位数组的概念。)

            一个很好且相当简单的例子是 Nim 游戏的一般解决方案。查看the Wikipedia page 上的Python 代码。它大量使用按位异或,^。

            【讨论】:

              【解决方案12】:

              可能有更好的方法来查找数组元素在两个值之间的位置,但正如此示例所示,& 在这里有效,而 而没有。

              import numpy as np
              a=np.array([1.2, 2.3, 3.4])
              np.where((a>2) and (a<3))      
              #Result: Value Error
              np.where((a>2) & (a<3))
              #Result: (array([1]),)
              

              【讨论】:

                【解决方案13】:

                我没有看到它提到,这个例子将向您展示 2 位值的 (-) 十进制运算:A-B(仅当 A 包含 B)

                当我们在程序中保存一个表示位的动词时,需要此操作。有时我们需要添加位(如上),有时我们需要删除位(如果动词包含 then)

                111 #decimal 7
                -
                100 #decimal 4
                --------------
                011 #decimal 3
                

                使用python: 7 & ~4 = 3(从 7 中删除代表 4 的位)

                001 #decimal 1
                -
                100 #decimal 4
                --------------
                001 #decimal 1
                

                使用python: 1 & ~4 = 1(从 1 中删除代表 4 的位 - 在这种情况下,1 不是“包含”4)..

                【讨论】:

                  【解决方案14】:

                  虽然操作整数的位很有用,但通常对于网络协议而言,可能会指定到位,但可能需要操作更长的字节序列(不容易转换为一个整数)。在这种情况下,使用bitstring 库非常有用,它允许对数据进行按位操作 - 例如可以将字符串 'ABCDEFGHIJKLMNOPQ' 作为字符串或十六进制导入并对其进行位移(或执行其他按位运算):

                  >>> import bitstring
                  >>> bitstring.BitArray(bytes='ABCDEFGHIJKLMNOPQ') << 4
                  BitArray('0x142434445464748494a4b4c4d4e4f50510')
                  >>> bitstring.BitArray(hex='0x4142434445464748494a4b4c4d4e4f5051') << 4
                  BitArray('0x142434445464748494a4b4c4d4e4f50510')
                  

                  【讨论】:

                    【解决方案15】:

                    以下位运算符:&、|、^ 和 ~ 以相同的方式返回值(基于它们的输入)logic gates 影响信号。您可以使用它们来模拟电路。

                    【讨论】:

                      【解决方案16】:

                      要翻转位(即 1 的补码/反转),您可以执行以下操作:

                      由于值与全 1 异或导致反转, 对于给定的位宽,您可以使用 ExOR 来反转它们。

                      In Binary
                      a=1010 --> this is 0xA or decimal 10
                      then 
                      c = 1111 ^ a = 0101 --> this is 0xF or decimal 15
                      -----------------
                      In Python
                      a=10
                      b=15
                      c = a ^ b --> 0101
                      print(bin(c)) # gives '0b101'
                      

                      【讨论】:

                        【解决方案17】:

                        您可以使用位掩码将二进制转换为十进制;

                        int a = 1 << 7;
                        int c = 55;
                        for(int i = 0; i < 8; i++){
                            System.out.print((a & c) >> 7);     
                            c = c << 1;
                        }
                        

                        这是 8 位数字,您还可以做更多。

                        【讨论】:

                          猜你喜欢
                          • 2013-10-21
                          • 2013-01-25
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          相关资源
                          最近更新 更多