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