Julia语言程序设计
上QQ阅读APP看书,第一时间看更新

4.2 位运算符

无论怎样的数值类型,在计算机中都是连续的二进制串。任何的运算实质都是在改变或转移这些二进制串。对数值进行位运算,包括与(And)、或(Or)、非(取反,Not)、异或(Xor)、左移及右移等时,实际是对数据的内存结构进行直接的操作。在一些情况下,直接控制这些二进制串,可以在节约计算资源或提升关键代码性能等方面带来好处。这样的操作常见于通信中的传输协议、编解码或密码学等领域。

需要说明的是,Julia中提供的位运算符仅适用于整型,不能是其他类型,包括浮点型或有理数型等。表4-2给出了Julia位运算符的示例用法及简单的说明。

表4-2 位运算符

其中除了取反是一元的,其他都是二元运算符。

1.与或非运算

我们以实例首先了解取反操作,例如:


julia> m = Int8(50)
50

julia> bitstring(m)                              # m值的内部二进制结构
"00110010"

julia> n = ~m
-51

julia> bitstring(n)                              # 取反后的二进制结构
"11001101"

变量m的内容是一个值为50的有符号整数。经过取反后,其内存中的每一个二进制位均被反转,甚至包括符号位。所以取反的结果是数值50变成了-50这个负值。

再看一个无符号数值的例子:


julia> m = UInt8(3)
0x03

julia> bitstring(m)
"00000011"

julia> n = ~m                                         # 对无符号数50取反
0xfc

julia> bitstring(n)
"11111100"

其中值为3的8位无符号整型m的各二进制位同样被逐位反转。因为是无符号的,所以不存在正负值变换的说法,但我们可以通过将其转换到Int64类型看一下值的变化:


julia> Int64(m)
3

julia> Int64(n)
252

这是显而易见的。至于为何是“显而易见”,读者可以研究一下。

对于与、或及异或运算,在符号变化方面有些不同,例如:


julia> m = Int8(10);
julia> n = Int8(-7);

julia> bitstring(m)                              # m的二进制结构
"00001010"

julia> bitstring(n)                              # n的二进制结构
"11111001"

julia> p = m & n                                         # 与操作
8

julia> q = m | n                                         # 或操作
-5

julia> r = m ˇ n                                         # 异或操作
-13

julia> bitstring(p)                              # 与结果的二进制结构
"00001000"

julia> bitstring(q)                                         # 或结果的二进制结构
"11111011"

julia> bitstring(r)                                         # 异或结果的二进制结构
"11110011"

这三个运算同样会涉及符号位,但结果的符号是可以根据运算的特点进行推断的。例如,正数与负数做与运算时,结果肯定是正数(因为符号位必然是0);而做或运算时,则会得到负数,因为符号位必然是1。

2.移位运算

同样,左移运算也会涉及符号位,例如:


julia> bitstring(m)
"00001010"

julia> bitstring(m << 1)                              # 左移1位
"00010100"

julia> bitstring(m << 2)                                         # 左移2位
"00101000"

julia> bitstring(m << 3)                                         # 左移3位
"01010000"

julia> m << 3                                         # 左移3位后的取值
80
julia> bitstring(m << 4)                                         # 左移4位
"10100000"

julia> m << 4                                         # 左移4位后的取值
-96

随着值为10的m变量的二进制位不断左移,值会越来越大,但当移动了4个位置后,符号位由0变成了1,m的值也变成了负数。

但右移运算中,Julia区分了是否考虑符号位的情况,分为算术右移及逻辑右移。在算术右移后,数值的正负是不变的,例如:


julia> n                                                        # n为负数
-7

julia> bitstring(n)
"11111001"

julia> n >> 1                                         # 右移1位
-4

julia> bitstring(ans)
"11111100"

变量n是负数,符号位是1,算术右移1位后,右侧的二进制位消失,但因为考虑了符号位,所以左侧在补充二进制位时,补上的并不是0而是1,从而保持了符号的表达。

继续对n进行算术右移,会发现:


julia> bitstring(n)                              # n的二进制结构
"11111001"

julia> bitstring(n >> 1)                                         # 算术右移1位
"11111100"

julia> bitstring(n >> 2)                                         # 算术右移2位
"11111110"

julia> bitstring(n >> 3)                                         # 算术右移3位
"11111111"

julia> bitstring(n >> 4)                                         # 算术右移4位
"11111111"

julia> n >> 4                                          # 算术右移4位后的取值
-1

当移动了一定的数位后,二进制串中不再有0,结果的值便保持了-1,再算术右移任意的位数,该值都不再变化。

而对于正数来说,不断右移的结果为:


julia> m
10

julia> bitstring(m)                                         # m的二进制结构
"00001010"

julia> bitstring(m >> 1)                                         # 算术右移1位
"00000101"

julia> bitstring(m >> 2)                                         # 算术右移2位
"00000010"

julia> bitstring(m >> 3)                                         # 算术右移3位
"00000001"

julia> bitstring(m >> 4)                                         # 算术右移4位
"00000000"

julia> bitstring(m >> 5)                                         # 算术右移5位
"00000000"

julia> m >> 5                                          # 算术右移5位后的取值
0

右移了一定位数后,结果值保持为0值,而且不再变化。

正数的这种情况在逻辑右移中也是一样的,例如:


julia> bitstring(m >>> 1)                # 逻辑右移1位
"00000101"

julia> bitstring(m >>> 2)                # 逻辑右移2位
"00000010"

julia> bitstring(m >>> 3)                # 逻辑右移3位
"00000001"

julia> bitstring(m >>> 4)                # 逻辑右移4位
"00000000"

julia> bitstring(m >>> 5)                # 逻辑右移5位
"00000000"

对负数来说,逻辑右移的效果类似,但是有所不同,例如:


julia> n
-7

julia> bitstring(n)
"11111001"

julia> p = n >>> 1                                         # 逻辑右移1位
124

julia> bitstring(p)
"01111100"

对于值为-7的n而言,仅移动了一位,结果的值便变成了124这个正数。

一般而言,任何负数只要经过一次逻辑右移便会变成正数,这是因为计算机表达正数的符号位是左侧的0,而一旦逻辑右移,1便会被0补上。

提示 在位运算方面,很多应用还会涉及循环左移或循环右移操作,有兴趣的读者可以查阅相关资料进行了解。