按位运算及用法
- 2024-12-02 08:41:00
- admin 原创
- 167
问题描述:
考虑以下代码:
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 书),我理解左移,但不理解其他两个。
另外,位运算符实际上有什么用处?我希望能够举一些例子。
解决方案 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)进行 AND 运算,因此:
201: 1100 1001
AND 15: 0000 1111
------------------
IS 9 0000 1001
在这种情况下,15 中的零位实际上充当了过滤器的角色,强制结果中的位也为零。
此外,>>
和<<
通常作为按位运算符包含在内,它们分别将值向右和向左“移动”一定数量的位,丢弃向其移动的一端滚动的位,并在另一端输入零位。
例如:
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)
该
& 15
操作将确保两个值都只有低 4 位。这
<< 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
&
用于测试或清除某个位
设置一个位(其中 n 是位数,0 是最低有效位):
unsigned char a |= (1 << n);
清楚一点:
unsigned char b &= ~(1 << n);
稍微切换一下:
unsigned char c ^= (1 << n);
测试一下:
unsigned char e = d & (1 << n);
以你的列表为例:
x | 2
用于将位 1 设置为x
1
x & 1
用于测试的位 0x
是 1 还是 0
解决方案 3:
位运算符实际上有什么用处?我希望能够举一些例子。
按位运算最常见的用途之一是解析十六进制颜色。
例如,这里有一个Python函数,它接受一个字符串#FF09BE
并返回其红色、绿色和蓝色值的元组。
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 和 x >> 1 来加倍和减半,虽然我不确定他们是否只是想耍小聪明,或者是否真的比普通运算符有明显的优势。
解决方案 6:
我希望这能澄清以下两个问题:
x | 2
0001 //x
0010 //2
0011 //result = 3
x & 1
0001 //x
0001 //1
0001 //result = 1
解决方案 7:
将 0 视为假,1 视为真。然后按位与(&) 和或(|) 的工作方式与常规与和或一样,只是它们一次处理值中的所有位。通常,如果您有 30 个可设置的选项(比如窗口上的绘制样式),您会看到它们用于标志,您不想传入 30 个单独的布尔值来设置或取消设置每个选项,因此您可以使用 | 将选项组合成一个值,然后使用 & 检查每个选项是否设置了。OpenGL 大量使用这种标志传递样式。由于每个位都是一个单独的标志,您可以获得 2 的幂的标志值(即只有一位设置的数字)1(2^0) 2(2^1) 4(2^2) 8(2^3),2 的幂告诉您如果标志打开,哪个位被设置了。
还要注意 2 = 10,所以 x|2 是 110(6) 而不是 111(7) 如果没有位重叠(在这种情况下是正确的)| 就像加法一样。
解决方案 8:
套
可以使用数学运算来组合集合。
并集运算符
|
将两个集合合并起来,形成一个包含任一集合中的项的新集合。交集运算符
&
仅获取两者中的项目。差分运算符
-
获取第一个集合中的项目,但不获取第二个集合中的项目。对称差分运算符
^
可以获取任一集合中的项,但不能同时获取两者中的项。
自己尝试一下:
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 stat 模块: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() 值是值得的练习。
解决方案 11:
在科学计算中,整数的位表示通常用于表示真假信息数组,因为按位运算比遍历布尔数组要快得多。(高级语言可能会使用位数组的概念。)
一个很好的、相当简单的例子是 Nim 游戏的通用解决方案。查看Wikipedia 页面上的Python代码。它大量使用了按位排他或。^
解决方案 12:
可能有更好的方法来找到数组元素在两个值之间的位置,但是正如这个例子所示,&在这里有效,而and无效。
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 位值的 (-) 十进制运算:AB(仅当 A 包含 B 时)
当我们在程序中保存代表位的动词时,需要进行此操作。有时我们需要添加位(如上所示),有时我们需要删除位(如果动词包含则)
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:
虽然操作整数的位很有用,但通常对于网络协议来说,这些协议可能指定到位,但可能需要操作较长的字节序列(不容易转换为一个整数)。在这种情况下,使用允许对数据进行按位运算的位串库很有用 - 例如,可以将字符串“ABCDEFGHIJKLMNOPQ”导入为字符串或十六进制并对其进行位移位(或执行其他按位运算):
>>> import bitstring
>>> bitstring.BitArray(bytes='ABCDEFGHIJKLMNOPQ') << 4
BitArray('0x142434445464748494a4b4c4d4e4f50510')
>>> bitstring.BitArray(hex='0x4142434445464748494a4b4c4d4e4f5051') << 4
BitArray('0x142434445464748494a4b4c4d4e4f50510')
解决方案 15:
以下按位运算符:&、|、^和~根据其输入返回值,其方式与逻辑门影响信号的方式相同。您可以使用它们来模拟电路。
解决方案 16:
要翻转位(即 1 的补码/反转),您可以执行以下操作:
由于值与全 1 进行异或运算的结果为反转,因此对于给定的位宽,可以使用异或运算来反转它们。
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 位数字,您还可以做更多。