一文搞定位运算
目錄
一、前言
二、加減乘除運算原理
2.1 加法和乘法
2.2 減法和除法
三、位運算
3.1 按位與 &?
3.2 按位或 |
3.3 按位異或 ^
3.4 取反 ~
3.5 按位左移<<
3.6 按位右移 >>
3.7 無符號按位右移 >>>??
3.8 總結(jié)
四、位運算應用
4.1 常用位運算
4.2 顏色轉(zhuǎn)換
4.3 枚舉
4.4 加密
位運算總結(jié):
| 按位與 & | 同1為1 |
| 按位或 | | 任1為1 |
| 按位異或 ^ | 同1為1 |
| 取反 ~ | 值取反 |
| 左移 >> | 廢棄左側(cè)指定位數(shù)后左移,空位補0 |
| 右移 << | 廢棄右側(cè)指定位數(shù)后右移,空位補0 |
| 無符號右移 <<< | 忽略符號位的右移 |
?
一、前言
在現(xiàn)代計算機中所有的數(shù)據(jù)都是以二進制的形式存儲在設備中。即0、1兩種狀態(tài),計算機對二進制數(shù)據(jù)進行的運算(+、-、*、/)都是叫位運算,即將符號位共同參與運算的運算。
我們每一種語言最終都會通過編譯器轉(zhuǎn)換成機器語言來執(zhí)行,所以直接使用底層的語言就不需要便編譯器的轉(zhuǎn)換工作從而得到更高的執(zhí)行效率,當然可讀性可能會降低,這也是為什么匯編在大部分情況下有更快的速度。項目中合理的運用位運算能提高我們代碼的執(zhí)行效率。
?
二、加減乘除運算原理
2.1 加法和乘法
舉一個簡單的例子來看下CPU是如何進行計算的,比如這行代碼
int a = 35;int b = 47;int c = a + b;計算兩個數(shù)的和,因為在計算機中都是以二進制來進行運算,所以上面我們所給的int變量會在機器內(nèi)部先轉(zhuǎn)換為二進制在進行相加
35: 0 0 1 0 0 0 1 147: 0 0 1 0 1 1 1 1————————————————————82: 0 1 0 1 0 0 1 0再來看下乘法,執(zhí)行如下的代碼
int a = 3; int b = 2; int c = a * b;3: 0 0 0 0 0 0 1 1 * 2————————————————————6: 0 0 0 0 0 1 1 0*********************************************int a = 3; int b = 4; int c = a * b;3: 0 0 0 0 0 0 1 1 * 4————————————————————12: 0 0 0 0 1 1 0 0*********************************************int a = 3; int b = 8; int c = a * b;3: 0 0 0 0 0 0 1 1 * 8————————————————————24: 0 0 0 1 1 0 0 0通過以上運算可以看出當用a乘b,且如果b滿足2^N的時候 就相當于把a的二進制數(shù)據(jù)向左移動N位,放到代碼中 我們可以這樣來寫 a << N,所以上面3 * 2、3 * 4、3 * 8其實是可以寫成3<<1、3<<2、3<<3,運算結(jié)果都是一樣的。
那假如相乘的兩個數(shù)都不滿足2N怎么辦呢?其實這個時候編譯器會將其中一個數(shù)拆分成多個滿足2N的數(shù)相加的情況,打個比方
int a = 15; int a = 15 int b = 13; => int b = (4 + 8 + 1) int c = a * b; int c = a * b
最后其實執(zhí)行相乘運算就會變成這樣 15 * 4 + 15 * 8 + 15 * 1,按照上文說的移位來轉(zhuǎn)換為位運算就會變成15 << 2 + 15 << 3 + 15 << 0
?
2.2 減法和除法
減法也是與加法同理只不過計算機內(nèi)減法操作就是加上一個數(shù)的負數(shù)形式,且在操作系統(tǒng)中都是以補碼的形式進行操作(因為正數(shù)的源碼補碼反碼都與本身相同)。首先, 因為人腦可以知道第一位是符號位, 在計算的時候我們會根據(jù)符號位, 選擇對真值區(qū)域的加減. 但是對于計算機, 加減乘數(shù)已經(jīng)是最基礎的運算, 要設計的盡量簡單. 計算機辨別"符號位"顯然會讓計算機的基礎電路設計變得十分復雜! 于是人們想出了將符號位也參與運算的方法. 我們知道, 根據(jù)運算法則減去一個正數(shù)等于加上一個負數(shù), 即: 1-1 = 1 + (-1) = 0 , 所以機器可以只有加法而沒有減法, 這樣計算機運算的設計就更簡單了.
除法的話其實和乘法原理相同,不過乘法是左移而除法是右移,但是除法的計算量要比乘法大得多,其大部分的消耗都在拆分數(shù)值,和處理小數(shù)的步驟上,所以如果我們在進行生成變量的時候如果遇到多位的小數(shù)我們盡量把他換成string的形式,這也是為什么浮點運算會消耗大量的時鐘周期。
操作系統(tǒng)中每進行一個移位或者加法運算的過程所消耗的時間就是一個時鐘周期,3.0GHz頻率的CPU可以在一秒執(zhí)行運算3.010241024*1024個時鐘周期
?
三、位運算
3.1 按位與 &?
針對二進制,只要有一個為0,就為0.
public static void main(String[] args) {System.out.println("2&3運算的結(jié)果是 :"+(2&3));//打印的結(jié)果是:?? 2&3運算的結(jié)果是 :2}?
3.2 按位或 |
針對二進制,只要有一個為1,就為1.
3.3 按位異或 ^
針對二進制,相同的為0,不同的為1
public static void main(String[] args) {System.out.println("2^3運算的結(jié)果是 :"+(2^3));//打印的結(jié)果是: 2^3運算的結(jié)果是 :1 } 2 =======>0010 3 =======>0011 2^3就為0001,結(jié)果就是13.4 取反 ~
針對二進制,1取0,0取1
3.5 按位左移<<
針對二進制,將操作數(shù)的所有位向左移動指定的位數(shù)。
desc:轉(zhuǎn)換成二進制后向左移動3位,后面用0補齊public static void main(String[] args) {System.out.println("2<<3運算的結(jié)果是 :"+(2<<3));//打印的結(jié)果是:?? 2<<3運算的結(jié)果是 :16}下圖展示了11111111 << 1(11111111 左移一位)的結(jié)果。
藍色數(shù)字表示被移動位,灰色表示被丟棄位,空位用橙色的0填充。
?
3.6 按位右移 >>
針對二進制,將操作數(shù)的所有位向又移動指定的位數(shù)。
desc:轉(zhuǎn)換成二進制后向右移動3位public static void main(String[] args) {System.out.println("2>>3運算的結(jié)果是 :"+(2>>3));//打印的結(jié)果是:?? 2>>3運算的結(jié)果是 :0}下圖展示了11111111 >> 1(11111111 右移一位)的結(jié)果。
藍色數(shù)字表示被移動位,灰色表示被丟棄位,空位用橙色的0填充。
?
3.7 無符號按位右移 >>>??
無符號右移,忽略符號位,空位都以0補齊。
二進制的最高位是符號位,0表示正,1表示負。
>>>與>>唯一的不同是它無論原來的最左邊是什么數(shù),統(tǒng)統(tǒng)都用0填充。
?
十進制轉(zhuǎn)二進制時,因為二進制數(shù)一般分8位、 16位、32位以及64位表示一個十進制數(shù),所以在轉(zhuǎn)換過程中,最高位會補零。在計算機中負數(shù)采用二進制的補碼表示,十進制轉(zhuǎn)為二進制得到的是源碼,將源碼按位取反得到的是反碼,反碼加1得到補碼。
比如byte是8位的,-1表示為byte型是11111111(補碼表示法),-1>>>4就是無符號右移4位,即00001111,這樣結(jié)果就是15。
public static void main(String[] args) {System.out.println("16>>2運算的結(jié)果是 :"+((16)>>2));//打印的結(jié)果是:?? 16>>2運算的結(jié)果是 :4}public static void main(String[] args) {System.out.println("-16>>2運算的結(jié)果是 :"+((-16)>>2));//打印的結(jié)果是:?? -16>>2運算的結(jié)果是 :-4}public static void main(String[] args) {System.out.println("16>>>2運算的結(jié)果是 :"+((16)>>>2));//打印的結(jié)果是:?? 16>>>2運算的結(jié)果是 :4}public static void main(String[] args) {System.out.println("-16>>>2運算的結(jié)果是 :"+((-16)>>>2));//打印的結(jié)果是:?? -16>>>2運算的結(jié)果是 :1073741820}可見正數(shù)做>>>運算的時候和>>是一樣的。區(qū)別在于負數(shù)運算
?
3.8 總結(jié)
位運算應用口訣?
清零取反要用與,某位置一可用或?
若要取反和交換,輕輕松松用異或?
?
移位運算要點?
?1 、它們都是雙目運算符,兩個運算分量都是整形,結(jié)果也是整形。?
?2 、右移運算符>>:右邊的位被擠掉,對于左邊多出的空位,如果是正數(shù)則空位補0,若為負數(shù),可能補0或補1,這取決于所用的計算機系統(tǒng)。?
?3、 左移運算符<<:左邊的位被擠掉,對于右邊移出的空位一概補上0。?
?
位運算符的應用?
參數(shù):源操作數(shù)s ,掩碼mask
(1)?按位與&?
? ? 1 、清零特定位?(mask中特定位置0,其它位為1,s=s&mask)?
? ? 2、 取某數(shù)中指定位?(mask中特定位置1,其它位為0,s=s&mask)?
(2)?按位或 |?
????常用來將源操作數(shù)某些位置1,其它位不變。
?(mask中特定位置1,其它位為0?s=s?|mask)?
(3)?位異或 ^?
? ? 1、 使特定位的值取反?(mask中特定位置1,其它位為0?s=s^mask)?
? ? 2 、不引入第三變量,交換兩個變量的值
?
二進制補碼運算公式
-x = ~x + 1 = ~(x-1)
~x = -x-1
-(~x) = x+1
~(-x) = x-1
x+y = x - ~y - 1 = (x|y)+(x&y)
x-y = x + ~y + 1 = (x|~y)-(~x&y)
x^y = (x|y)-(x&y)
x|y = (x&~y)+y
x&y = (~x|y)-~x
x==y:????~(x-y|y-x)
x!=y:????x-y|y-x
x< y:????(x-y)^((x^y)&((x-y)^x))
x<=y:????(x|~y)&((x^y)|~(y-x))
x< y:????(~x&y)|((~x|y)&(x-y))//無符號x,y比較
x<=y:????(~x|y)&((x^y)|~(y-x))//無符號x,y比較
?
四、位運算應用
4.1 常用位運算
(1) 判斷int型變量a是奇數(shù)還是偶數(shù)???????????
???????a&1???= 0 偶數(shù)
???????a&1 =???1 奇數(shù)
(2) 取int型變量a的第k位 (k=0,1,2……sizeof(int)),即a>>k&1
(3) 將int型變量a的第k位清0,即a=a&~(1<<k)
(4) 將int型變量a的第k位置1,即a=a|(1<<k)
(5) int型變量循環(huán)左移k次,即a=a<<k|a>>16-k???(設sizeof(int)=16)
(6) int型變量a循環(huán)右移k次,即a=a>>k|a<<16-k???(設sizeof(int)=16)
(7)整數(shù)的平均值
對于兩個整數(shù)x,y,如果用 (x+y)/2 求平均值,會產(chǎn)生溢出,因為 x+y 可能會大于INT_MAX,但是我們知道它們的平均值是肯定不會溢出的,我們用如下算法:
int average(int x, int y)???//返回X,Y 的平均值{???return (x&y)+((x^y)>>1);}boolean power2(int x){return ((x&(x-1))==0)&&(x!=0);}(9)不用temp交換兩個整數(shù)
三種交換算法的總結(jié) t=a; a=b; b=t; 優(yōu)點:不會溢出,可用于多種數(shù)據(jù)類型(指針,字符串等) 缺點:多用一個變量 a=a+b; b=a-b; a=a-b; 優(yōu)點:不增加變量。缺點:可能數(shù)值溢出。不能用于非數(shù)值交換
?a ^= b; b ^= a; a ^= b; (a^=b^=a^=b;) 優(yōu)點:速度快,不會數(shù)值溢出 缺點:只能用于整型量交換
?
(10)計算絕對值
int abs( int x ){int y ;y = x >> 31 ;return (x^y)-y ;????????//or: (x+y)^y}(11)取模運算轉(zhuǎn)化成位運算 (在不產(chǎn)生溢出的情況下):a % (2^n) 等價于 a & (2^n - 1)
(12)乘法運算轉(zhuǎn)化成位運算 (在不產(chǎn)生溢出的情況下):a * (2^n) 等價于 a<< n
(13)除法運算轉(zhuǎn)化成位運算 (在不產(chǎn)生溢出的情況下):a / (2^n) 等價于 a>> n
????????例: 12/8 == 12>>3
(14) a % 2 等價于 a & 1???????
(15) if (x == a) x= b; else x= a; 等價于 x= a ^ b ^ x;
(16) x 的相反數(shù) 表示為 (~x+1)
?
4.2 顏色轉(zhuǎn)換
背景
打個比方設計師再給我們出設計稿的時候通常會在設計稿上按照16進制的樣子給我們標色值。但是iOS中的UIColor并不支持使用十六進制的數(shù)據(jù)來初始化。所以我們需要將十六進制的色值轉(zhuǎn)換為UIColor。
?
原理分析
UIColor中通常是用傳入RGB的數(shù)值來初始化,而且每個顏色的取值范圍是十進制下的0~255,而設計同學又給的是十六進制數(shù)據(jù),所以在操作系統(tǒng)中需要把這兩種進制的數(shù)據(jù)統(tǒng)一成二進制來進行計算,這就用到了位運算。這里用一個十六進制的色值來舉例子比如0xffa131我們要轉(zhuǎn)換就要先理解其組成
- 0x或者0X:十六進制的標識符,表示這個后面是個十六進制的數(shù)值,對數(shù)值本身沒有任何意義
- ff 顏色中的R值,轉(zhuǎn)換為二進制為 1111 1111
- a1 顏色中的G值,轉(zhuǎn)換為二進制為 1010 0001
- 31 顏色中的B值,轉(zhuǎn)換為二進制為 0011 0001
- 上述色彩值轉(zhuǎn)換為二進制后為1111 1111 1010 0001 0011 0001(每一位十六進制的對應4位二進制,如果位數(shù)不夠記得高位補零)
通常來講十六進制的顏色是按照上面的RGB的順序排列的,但是并不固定,有時候可能會在其中加A(Alpha)值,具體情況按照設計為準,本文以通用情況舉例。
綜上,我們只需把對應位的值轉(zhuǎn)換為10進制然后/255.0f就可得到RGB色彩值,從而轉(zhuǎn)換為UIColor。
?
轉(zhuǎn)換代碼
先列出代碼,后續(xù)解析
- (UIColor *)colorWithHex:(long)hexColor alpha:(float)opacity {//將傳入的十六進制顏色0xffa131 轉(zhuǎn)換為UIColorfloat red = ((hexColor & 0xFF0000) >> 16)/255.0f;float green = ((hexColor & 0xFF00) >> 8)/255.0f;float blue = (hexColor & 0xFF)/255.0f;return [UIColor colorWithRed:red green:green blue:blue alpha:opacity]; }大概原理可以看出將RGB每個值都解析出來然后變成UIColor,先拿第一步轉(zhuǎn)換紅色值來說,我們按照運算順序一步步來講(默認將參數(shù)代入,用0xffa131代替hexColor)
- 0xffa131 & 0xFF0000
我們知道紅色值是前兩位也就是ff,所以這一步我們既然要取出紅色值就要把其他位全部置零來排除干擾,這步操作便是如此,在計算機系統(tǒng)內(nèi)是二進制來實現(xiàn)的,即:
?
1111 1111 1010 0001 0011 0001
------------------------------------------- => & => 1111 1111 0000 0000 0000
?
1111 1111 0000 0000 0000 0000
這部操作做完后可以看出將除了R值之外的G值B值全部置零了,但是離最終結(jié)果還差點,因為0xFF是1111 1111,而我們的結(jié)果后面多出了16個0,所以便有了第二步操作
- >> 16
將上一步得到的結(jié)果右移16位即得到0000 0000 0000 0000 1111 1111高位的零可以忽略,這也是最終的結(jié)果
- / 255.0f
這一步應該都知道UIColor中傳入的數(shù)值范圍在0~1,所以我們要做下轉(zhuǎn)換
- 后續(xù)的G值和B值都是一樣的,只是大家注意位數(shù)就可以了,值得注意的是兩個二進制數(shù)進行位運算一定保證兩個數(shù)的位數(shù)相同,位數(shù)不夠的那個數(shù)高位要用0補齊
?
4.3 枚舉
關于枚舉中使用位運算我們之前也講過,下面我們自己來寫一個枚舉(偽代碼)
typedef NS_OPTIONS(NSUInteger, TestOptions) {TestOptionOne = 1 << 0, (000001)TestOptionTwo = 1 << 1, (000010)TestOptionThree = 1 << 2, (000100)TestOptionFour = 1 << 3, (001000)TestOptionFive = 1 << 4, (010000)TestOptionSix = 1 << 5, (100000)上面的枚舉我后面用括號表明了位移后對應的二進制的值。這樣寫枚舉的好處是我可以對其中選項多選比如TestOptionOne | TestOptionTwo (000001 | 000010 => 000011) 或者有其他的自定義組合。
?
4.4 加密
在iOS中我們可以利用異或來進行加解密,異或的特性如下
A ^ B = C => C ^ A = B => C ^ B = A
上文我們可以把A認為是需要加密的數(shù)據(jù),B認為是密鑰 C是加密后的數(shù)據(jù),比如:
#include <stdio.h> main() {char a[]="MyPassword"; /*要加密的密碼*/char b[]="cryptographic"; /*密鑰*/int i;/*加密代碼*/for(i=0;a[i]!='\0';i++)a[i]=a[i]^b[i];printf("You Password encrypted: %s\n",a);/*解密代碼*/for(i=0;a[i]!='\0';i++)a[i]=a[i]^b[i];printf("You Password: %s\n",a); }?
總結(jié)
- 上一篇: Element 'dependency'
- 下一篇: 【KVM系列01】KVM简介及安装