目录
数值表示
用逻辑运算符进行位掩码
移位
写入位
读取连续位
读取非连续位
如何在 MATLAB® 中使用按位运算来操作数字的位。大多数现代处理器直接支持位运算。在许多情况下,以这种方式操作数字的位比执行除法或乘法等算术运算更快。
任何数值都可以用位来表示(也称为二进制位)。数值的二进制(即基数为 2)形式包含 1 和 0,以此表示数值中都存在 2 的哪些次幂。例如,7 的 8 位二进制形式是
00000111
8 个位的集合也称为 1 个字节。在二进制表示中,位从右向左计数,因此该表示中的第一个位是 1。此数值表示 7,因为
22+21+20=7
当在 MATLAB 中键入数值时,它假设数值为双精度(64 位二进制表示)。但是,也可以指定单精度数(32 位二进制表示)和整数(有符号或无符号,从 8 到 64 位)。例如,要存储数值 7,最节省内存的方法是使用 8 位无符号整数:
a = uint8(7)
a = uint8
7
甚至可以直接使用前缀 0b
后跟二进制数字来指定二进制形式(有关详细信息,可以参考十六进制和二进制值)。MATLAB 以位数最少的整数格式存储数值。只需指定最左边的 1 和它右边的所有位,而不必指定所有位。该位左边的位是无效零。因此数值 7 是:
b = 0b111
b = uint8
7
MATLAB 使用 2 的补码存储负整数。以 8 位有符号整数 -8 为例。要找到该数值的 2 的补码位模式,请执行以下操作:
首先找到该数值对应的正数 8 的位模式:00001000
。
接下来,翻转所有位:11110111
。
最后,对结果加 1:11111000
。
得到的 11111000
是 -8 的位模式:
n = 0b11111000s8
n = int8
-8
MATLAB 并不主动显示数值的二进制格式。为此,可以使用dec2bin函数,该函数会返回正整数的二进制数字字符向量。同样,此函数只返回不包含无效零的位。
dec2bin(b)
ans =
'111'
可以使用bin2dec在这两种格式之间切换。例如,可以使用以下命令将二进制数字 10110101
转换为十进制格式:
data = [1 0 1 1 0 1 0 1];
dec = bin2dec(num2str(data))
dec = 181
cast 和 typecast 函数也可用于不同数据类型之间的切换。这些函数是相似的,但它们在如何处理数值的底层存储方面有所不同:
cast- 更改变量的基础数据类型。
typecast - 转换数据类型而不更改基础位。
由于 MATLAB 不直接显示二进制数的位,在进行按位运算时必须注意数据类型。有些函数以字符向量形式返回二进制数字 (dec2bin
),有些函数返回十进制数 (bitand
),还有一些函数返回由位本身组成的向量 (bitget
)。
使用 MATLAB 中的一些函数,可以对以等长二进制表示的两个数值中的位执行逻辑运算,此种运算称为位掩码:
bitand - 如果两个位均为 1,则结果位也是 1。否则,结果位为 0。
bitor - 如果任一位是 1,则结果位也是 1。否则,结果位为 0。
bitxor - 如果位不同,则结果位为 1。否则,结果位为 0。
除了这些函数之外,还可以使用bitcmp进行按位补码,但这是一元运算,一次只能翻转一个数值中的位。
位掩码的一个用途是查询特定位的状态。例如,如果对二进制数 00001000
进行按位 AND 运算,可以查询第四个位的状态。然后,可以将该位移至第一个位置,以便 MATLAB 返回 0 或 1(下一节将更详细地说明位移)。
n = 0b10111001;
n4 = bitand(n,0b1000);
n4 = bitshift(n4,-3)
n4 = uint8
1
按位运算有时可以发挥意想不到的作用。例如,以下是数值 n=8 的 8 位二进制表示:
00001000
8 是 2 的幂,因此它的二进制表示只包含一个 1。现在考虑数值 (n−1)=7:
00000111
由于减去了 1,从最右边的 1 开始的所有位都会翻转。因此,当 n 是 2 的幂时,n 和 (n−1) 的对应位始终不同,使得按位 AND 返回零。
n = 0b1000;
bitand(n,n-1)
ans = uint8
0
但是,如果 n 不是 2 的幂,则最右边的 1 表示 20 位,因此 n 和 (n−1) 除了 20 位之外,其他位都相同。在这种情况下,按位 AND 返回一个非零数值。
n = 0b101;
bitand(n,n-1)
ans = uint8
4
受上述运算启发,我们可以编写一个简单的函数对给定的输入数值执行位运算,以判断它是否为 2 的幂:
function tf = isPowerOfTwo(n)
tf = n && ~bitand(n,n-1);
end
使用短路 AND 运算符 && 检查以确保 n 不为零。如果为零,则该函数不需要计算 bitand(n,n-1) 即可知道正确答案是 false。
由于按位逻辑运算比较两个数值中对应的位,因此,能够按需移位以便于比较对应位就显得非常重要。可以使用bitshift执行此操作:
bitshift(A,N)
将 A
的位向左移动 N
位。这等效于将 A
和 2N 相乘。
bitshift(A,-N)
将 A
的位向右移动 N
位。这等效于将 A
除以 2N。
上述操作有时写作 A<
A>>N
(右移),但 MATLAB 没有将 <<
和 >>
运算符用于此目的。
当数值的位发生移动时,数值会从末尾丢弃一些位,并引入 0
或 1
来填充新腾出的空间。当向左移动位时,右端发生位填充;当向右移动位时,左端发生位填充。
例如,如果将数值 8(二进制:1000
)右移一位,则得到 4(二进制:100
)。
n = 0b1000;
bitshift(n,-1)
ans = uint8
4
同样,如果将数值 15(二进制:1111
)左移两位,则得到 60(二进制:111100
)。
n = 0b1111;
bitshift(15,2)
ans = 60
当移动负数的位时,bitshift
会保留有符号位。例如,如果将有符号整数 -1(二进制:11111101
)向右移动 2 位,则得到 -1(二进制:11111111
)。在这些情况下,bitshift
在左端填充 1
而不是 0
。
n = 0b11111101s8;
bitshift(n,-2)
ans = int8
-1
可以使用bitset函数来更改数值中的位。例如,将数值 8 的第一个位更改为 1(相当于将该数值加 1):
bitset(8,1)
ans = 9
默认情况下,bitset 将位翻转为 on 或 1。可以选择使用第三个输入参数来指定位值。
bitset 不会一次更改多个位;要更改多个位,您需要使用 for 循环。因此,更改的位可以是连续的,也可以是非连续的。例如,更改二进制数 1000 的前两位:
bits = [1 2];
c = 0b1000;
for k = 1:numel(bits)
c = bitset(c,bits(k));
end
dec2bin(c)
ans =
'1011'
bitset 的另一个常见用途是将二进制数字向量转换为十进制格式。例如,使用循环来设置整数 11001101 的各个位。
data = [1 1 0 0 1 1 0 1];
n = length(data);
dec = 0b0u8;
for k = 1:n
dec = bitset(dec,n+1-k,data(k));
end
dec
dec = uint8
205
dec2bin(dec)
ans =
'11001101'
位移的另一个用途是隔离位的连续部分。例如,读取 16 位数值 0110000010100000
中的最后四位。前面提到,最后四位位于二进制表示的左端。
n = 0b0110000010100000;
dec2bin(bitshift(n,-12))
ans =
'110'
要隔离该数值中间的连续位,可以结合使用位移和逻辑掩码。例如,要提取第 13 位和第 14 位,可以向右移动 12 位,然后用 0011 对所得的 4 位进行掩码。由于 bitand 的输入必须为相同的整数数据类型,可以使用 0b11u16 将 0011 指定为无符号 16 位整数。如果没有 -u16 后缀,MATLAB 会将数值存储为无符号 8 位整数。
m = 0b11u16;
dec2bin(bitand(bitshift(n,-12),m))
ans =
'10'
读取连续位的另一种方法是使用 bitget
,它从数值中读取指定的位。可以使用冒号表示法指定要读取的几个连续位。例如,读取 n
的最后 8 位。
bitget(n,16:-1:8)
ans = 1x9 uint16 row vector
0 1 1 0 0 0 0 0 1
也可以使用 bitget
从数值中读取彼此不相邻的位。例如,从 n
中读取第 5、8 和 14 位。
bits = [14 8 5];
bitget(n,bits)
ans = 1x3 uint16 row vector
1 1 0