我一直在尝试在业余时间学习C,而其他语言(C#、Java等)都有相同的概念(通常是相同的运算符)。。。
在核心层面,比特移位(<<,>>,>>)做什么,它可以帮助解决什么问题,以及弯道周围潜伏着什么陷阱?换言之,这是一个绝对的初学者指南,可以让你充分利用它的优点。
我一直在尝试在业余时间学习C,而其他语言(C#、Java等)都有相同的概念(通常是相同的运算符)。。。
在核心层面,比特移位(<<,>>,>>)做什么,它可以帮助解决什么问题,以及弯道周围潜伏着什么陷阱?换言之,这是一个绝对的初学者指南,可以让你充分利用它的优点。
当前回答
我只写提示和技巧。它可能在测试和考试中有用。
n=n*2:n=n<<1n=n/2:n=n>>1检查n是否为2的幂(1,2,4,8,…):检查!(n和(n-1))获取n:n|=(1<<x)的第x位检查x是偶数还是奇数:x&1==0(偶数)切换x:x^(1<<n)的第n位
其他回答
假设我们有一个字节:
0110110
应用一个左移位可以让我们:
1101100
最左边的零被移出字节,并在字节的右端附加一个新的零。
比特不会滚动;它们被丢弃。这意味着如果你左移1101100,然后右移它,你不会得到同样的结果。
向左移动N等于乘以2N。
向右移动N(如果你使用的是1的补码)相当于除以2N并舍入到零。
比特移位可以用于极快的乘法和除法,前提是你使用的是2的幂。几乎所有低级图形例程都使用位移位。
例如,在过去,我们使用模式13h(320x200 256色)进行游戏。在模式13h中,视频存储器按每个像素顺序布置。这意味着要计算像素的位置,需要使用以下数学公式:
memoryOffset = (row * 320) + column
现在,回到那个时代,速度是至关重要的,所以我们将使用比特移位来执行这个操作。
然而,320不是二的幂,所以为了解决这个问题,我们必须找出什么是二的幂加在一起就等于320:
(row * 320) = (row * 256) + (row * 64)
现在我们可以将其转换为左移:
(row * 320) = (row << 8) + (row << 6)
最终结果如下:
memoryOffset = ((row << 8) + (row << 6)) + column
现在我们得到了与之前相同的偏移量,除了不是昂贵的乘法运算,我们使用了两位移位。。。在x86中,它应该是这样的(注意,自从我完成汇编以来,它一直都是这样(编者注:更正了几个错误并添加了一个32位示例):
mov ax, 320; 2 cycles
mul word [row]; 22 CPU Cycles
mov di,ax; 2 cycles
add di, [column]; 2 cycles
; di = [row]*320 + [column]
; 16-bit addressing mode limitations:
; [di] is a valid addressing mode, but [ax] isn't, otherwise we could skip the last mov
总计:在任何古代CPU上都有28个周期。
Vrs
mov ax, [row]; 2 cycles
mov di, ax; 2
shl ax, 6; 2
shl di, 8; 2
add di, ax; 2 (320 = 256+64)
add di, [column]; 2
; di = [row]*(256+64) + [column]
12个周期。
是的,我们会努力工作以减少16个CPU周期。
在32位或64位模式下,这两个版本都变得更短更快。现代无序执行CPU,如Intel Skylake(参见http://agner.org/optimize/)具有非常快的硬件乘法(低延迟和高吞吐量),因此增益要小得多。AMD推土机系列速度稍慢,尤其是64位乘法。在Intel CPU和AMD Ryzen上,两个移位的延迟稍低,但指令比乘法多(这可能导致吞吐量降低):
imul edi, [row], 320 ; 3 cycle latency from [row] being ready
add edi, [column] ; 1 cycle latency (from [column] and edi being ready).
; edi = [row]*(256+64) + [column], in 4 cycles from [row] being ready.
vs.
mov edi, [row]
shl edi, 6 ; row*64. 1 cycle latency
lea edi, [edi + edi*4] ; row*(64 + 64*4). 1 cycle latency
add edi, [column] ; 1 cycle latency from edi and [column] both being ready
; edi = [row]*(256+64) + [column], in 3 cycles from [row] being ready.
编译器将为您做到这一点:了解GCC、Clang和Microsoft Visual C++在优化返回320*row+col;时如何使用shift+lea;。
这里最值得注意的是,x86有一个移位和加法指令(LEA),它可以同时执行小的左移位和加法,性能与加法指令相同。ARM甚至更强大:任何指令的一个操作数都可以自由左移或右移。因此,通过已知为2次幂的编译时间常数进行缩放甚至比乘法更有效。
好吧,回到现代。。。现在更有用的方法是使用位移位将两个8位值存储在16位整数中。例如,在C#中:
// Byte1: 11110000
// Byte2: 00001111
Int16 value = ((byte)(Byte1 >> 8) | Byte2));
// value = 000011111110000;
在C++中,如果您使用具有两个8位成员的结构,编译器应该为您做这件事,但实际上并不总是这样。
请注意,Windows平台上只有32位版本的PHP可用。
然后,如果您将<<或>>移位超过31位,则结果是不可预测的。通常会返回原始数字而不是零,这可能是一个非常棘手的错误。
当然,如果您使用64位版本的PHP(Unix),应该避免移位超过63位。然而,例如,MySQL使用64位BIGINT,因此不应该有任何兼容性问题。
更新:从PHP 7 Windows,PHP构建最终能够使用完整的64位整数:整数的大小取决于平台,尽管通常的最大值约为20亿(即32位带符号)。64位平台的最大值通常约为9E18,除了在PHP 7之前的Windows上,它总是32位。
包括位移位在内的按位操作是低级硬件或嵌入式编程的基础。如果您阅读设备规范或甚至某些二进制文件格式,您将看到字节、单词和dword,它们被分解为非字节对齐的位字段,其中包含各种感兴趣的值。访问这些位字段进行读/写是最常见的用法。
图形编程中的一个简单的实际示例是16位像素表示如下:
bit | 15| 14| 13| 12| 11| 10| 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| Blue | Green | Red |
要获得绿色值,您可以执行以下操作:
#define GREEN_MASK 0x7E0
#define GREEN_OFFSET 5
// Read green
uint16_t green = (pixel & GREEN_MASK) >> GREEN_OFFSET;
解释
为了获得从偏移量5开始到10结束(即6位长)的绿色ONLY值,您需要使用(位)掩码,当对整个16位像素应用该掩码时,将仅产生我们感兴趣的位。
#define GREEN_MASK 0x7E0
适当的掩码为0x7E0,二进制为00000111111000000(十进制为2016)。
uint16_t green = (pixel & GREEN_MASK) ...;
要应用掩码,请使用AND运算符(&)。
uint16_t green = (pixel & GREEN_MASK) >> GREEN_OFFSET;
应用掩码后,您将得到一个16位数字,这实际上只是一个11位数字,因为它的MSB位于第11位。绿色实际上只有6位长,因此我们需要使用右移(11-6=5)来缩小它,因此使用5作为偏移量(#define Green_offset 5)。
同样常见的是使用比特移位进行2次幂的快速乘法和除法:
i <<= x; // i *= 2^x;
i >>= y; // i /= 2^y;
我只写提示和技巧。它可能在测试和考试中有用。
n=n*2:n=n<<1n=n/2:n=n>>1检查n是否为2的幂(1,2,4,8,…):检查!(n和(n-1))获取n:n|=(1<<x)的第x位检查x是偶数还是奇数:x&1==0(偶数)切换x:x^(1<<n)的第n位
位屏蔽和移位
位移位通常用于低级图形编程。例如,以32位字编码的给定像素颜色值。
Pixel-Color Value in Hex: B9B9B900
Pixel-Color Value in Binary: 10111001 10111001 10111001 00000000
为了更好地理解,相同的二进制值标记了什么部分代表什么颜色部分。
Red Green Blue Alpha
Pixel-Color Value in Binary: 10111001 10111001 10111001 00000000
举个例子,我们想得到这个像素颜色的绿色值。我们可以很容易地通过掩蔽和转移来获得该值。
我们的口罩:
Red Green Blue Alpha
color : 10111001 10111001 10111001 00000000
green_mask : 00000000 11111111 00000000 00000000
masked_color = color & green_mask
masked_color: 00000000 10111001 00000000 00000000
逻辑&运算符确保只保留掩码为1的值。我们现在要做的最后一件事,就是通过将所有这些位右移16位(逻辑右移)来获得正确的整数值。
green_value = masked_color >>> 16
瞧,我们有一个整数表示像素颜色中的绿色量:
Pixels-Green Value in Hex: 000000B9
Pixels-Green Value in Binary: 00000000 00000000 00000000 10111001
Pixels-Green Value in Decimal: 185
这通常用于编码或解码图像格式,如jpg、png等。