我不知道的按位運(yùn)算

??計(jì)算機(jī)為什么會加減乘除骚揍?筆者是一直都沒了解過,認(rèn)為加減乘除就是理所當(dāng)然的事情啰挪,但計(jì)算機(jī)中的萬物皆為0信不、1嘲叔,筆者的認(rèn)為是不可能那么簡單的。

其實(shí)說直接點(diǎn)抽活,就是計(jì)算機(jī)的原理是什么硫戈?

1. 二進(jìn)制

二進(jìn)制就是計(jì)算機(jī)使用的“語言”。簡單舉例來說就是:人類使用十進(jìn)制來計(jì)數(shù)酌壕,它的運(yùn)算規(guī)則是“逢十進(jìn)一”掏愁。二進(jìn)制就是“逢二進(jìn)一”的運(yùn)算規(guī)則。

為什么計(jì)算機(jī)使用二進(jìn)制呢卵牍?這個(gè)可以歸結(jié)于一個(gè)人有十根手指果港,所以人類普遍認(rèn)知是使用十進(jìn)制。計(jì)算機(jī)是由一大堆電子電路組成的糊昙,使用二進(jìn)制正好對應(yīng)電路里的高低電平(大部分電子器件只有兩種狀態(tài))辛掠,這就好比計(jì)算機(jī)只有“兩根手指”。

1.1 原碼

原碼是二進(jìn)制的一種表現(xiàn)形式释牺。其為一個(gè)整數(shù)絕對值的二進(jìn)制萝衩,加上符號位(0為正,1為負(fù))没咙。

整數(shù) 絕對值 絕對值的二進(jìn)制 原碼
+3 3 000 0011 0000 00011
+3 3 000 0011 1000 00011
  • 原碼是給人看的二進(jìn)制猩谊,不是計(jì)算機(jī)保存的,不直接參與計(jì)算祭刚。

1.2 反碼

針對負(fù)數(shù)做處理牌捷,在原碼基礎(chǔ)上,除了符號位涡驮,其它位取反暗甥。

整數(shù) 絕對值的二進(jìn)制 原碼 反碼
+3 000 0011 0000 00011 0000 00011
+3 000 0011 1000 00011 1111 11100
  • 反碼是在計(jì)算機(jī)存儲的二進(jìn)制,但不是真正的二進(jìn)制值捉捅,它不直接參與計(jì)算撤防。

1.3 補(bǔ)碼

補(bǔ)碼是真正的二進(jìn)制值,主要針對負(fù)數(shù)做處理棒口,在反碼的基礎(chǔ)上加1寄月。

整數(shù) 原碼 反碼 補(bǔ)碼
+3 0000 00011 0000 00011 0000 00011
+3 1000 00011 1111 11100 1111 11101

1.4 理解補(bǔ)碼

假如一個(gè)時(shí)鐘現(xiàn)在顯示的是10點(diǎn)鐘,如何將它調(diào)到6點(diǎn)鐘无牵?

解:有兩種方法漾肮,一是向后撥8個(gè)小時(shí),二是向前撥4個(gè)小時(shí)

在這個(gè)例子中合敦,8 和 4 互為補(bǔ)數(shù)初橘,也就是說4的補(bǔ)碼是8,8的補(bǔ)碼是4,而這個(gè)時(shí)鐘的模就是12

注意:可能有人會想,在往后調(diào)8個(gè)小時(shí)雖然也調(diào)到了6點(diǎn)保檐,但是他實(shí)際上比原來日期多了12小時(shí)耕蝉。是的,的確如此夜只,但是你的時(shí)鐘有地方存儲了這多余的12個(gè)小時(shí)嗎垒在?答案是沒有,所以在你調(diào)完后扔亥,你沒有記錄這12個(gè)小時(shí)场躯,換句話說,你把這溢出的12個(gè)小時(shí)自動(dòng)舍棄了旅挤。當(dāng)?shù)诙€(gè)人來查看鬧鐘時(shí)間的時(shí)候踢关,他看到的時(shí)間就是準(zhǔn)的。

二進(jìn)制補(bǔ)碼表示負(fù)數(shù)是最方便的方式粘茄,它的便利體現(xiàn)在签舞,所有的加法運(yùn)算(加正數(shù)、加負(fù)數(shù))可以使用同一種電路完成柒瓣。

我們以-8作為例子儒搭。

假定有兩種表示方法。一種是直覺表示法芙贫,即10001000搂鲫;另一種是2的補(bǔ)碼表示法,即11111000磺平。請問哪一種表示法在加法運(yùn)算中更方便魂仍?

隨便寫一個(gè)計(jì)算式,16 + (-8) = ?其中褪秀,16的二進(jìn)制表示是 00010000蓄诽,-8的二進(jìn)制表示是 10001000

  • 直覺表示法:
⊙ρ怠00010000
+10001000
---------
∶铰稹10011000

可以看到,如果按照正常的加法規(guī)則乙埃,就會得到10011000的結(jié)果闸英,轉(zhuǎn)成十進(jìn)制就是-24。顯然介袜,這是錯(cuò)誤的答案甫何。也就是說,在這種情況下遇伞,正常的加法規(guī)則不適用于正數(shù)與負(fù)數(shù)的加法辙喂,因此必須制定兩套運(yùn)算規(guī)則,一套用于正數(shù)加正數(shù),還有一套用于正數(shù)加負(fù)數(shù)巍耗。從電路上說秋麸,就是必須為加法運(yùn)算做兩種電路。

  • 補(bǔ)碼表示法:
【嫣00010000
+11111000
---------
100001000

可以看到灸蟆,按照正常的加法規(guī)則,得到的結(jié)果是100001000亲族。注意炒考,這是一個(gè)9位的二進(jìn)制數(shù)。我們已經(jīng)假定這是一臺8位機(jī)霎迫,因此最高的第9位是一個(gè)溢出位斋枢,會被自動(dòng)舍去。所以知给,結(jié)果就變成了00001000杏慰,轉(zhuǎn)成十進(jìn)制正好是8,也就是16 + (-8) 的正確答案炼鞠。這說明了缘滥,2的補(bǔ)碼表示法可以將加法運(yùn)算規(guī)則,擴(kuò)展到整個(gè)整數(shù)集谒主,從而用一套電路就可以實(shí)現(xiàn)全部整數(shù)的加法朝扼。

2. 按位運(yùn)算

Python為例,常用位運(yùn)算符包括以下6中:

  • 按位與 &
  • 按位或 |
  • 按位異或 ^
  • 按位翻轉(zhuǎn) ~
  • 左移運(yùn)算 <<
  • 右移運(yùn)算 >>

2.1 按位與(&)

即按照對應(yīng)位置的二進(jìn)制進(jìn)行 and運(yùn)算霎肯,其中運(yùn)算規(guī)則為 1 & 1 = 1擎颖,1 & 0 = 00 & 1 = 0观游,0 & 0 = 0搂捧。

下面的 5 & 3示意如下:

十進(jìn)制     二進(jìn)制
5 --> 0000 0101
  &        &&&&
3 --> 0000 0011
  =        ||||
1 <-- 0000 0001
  • 位運(yùn)算判斷奇偶性

n&1 == 1 # 奇數(shù)返回1,偶數(shù)返回0

2.2 按位或(|)

即按照對應(yīng)位置的二進(jìn)制進(jìn)行 or運(yùn)算懂缕,其中的運(yùn)算規(guī)則為 1 | 1 = 1允跑,1 | 0 = 10 | 1 = 1搪柑,0 | 0 = 0

下面的 5 | 3示意如下:

十進(jìn)制     二進(jìn)制
5 --> 0000 0101
  |        ||||
3 --> 0000 0011
  =        ||||
7 <-- 0000 0111
  • 向上取奇數(shù)

map(lambda x:x|1, range(6)) # 結(jié)果為[1, 1, 3, 3, 5, 5]

2.3 按位異或(^)

即按照對應(yīng)位置的二進(jìn)制進(jìn)行 xor運(yùn)算聋丝,其中的運(yùn)算規(guī)則為 1 ^ 1 = 01 ^ 0 = 1工碾,0 ^ 1 = 1弱睦,0 ^ 0 = 0

下面的 5 ^ 3示意如下:

十進(jìn)制     二進(jìn)制
5 --> 0000 0101
  ^        ^^^^
3 --> 0000 0011
  =        ||||
6 <-- 0000 0110
  • 只出現(xiàn)一次的數(shù)字

reduce(lambda x,y: x^y, [1,1,3,2,4,3,4]) # 累積進(jìn)行異或運(yùn)算,找出只出現(xiàn)一次的數(shù)字為2渊额,出現(xiàn)兩次的數(shù)字就抵消掉了

2.4 按位翻轉(zhuǎn)(~)

即1變成0况木,0變成1垒拢。

十進(jìn)制     二進(jìn)制
5 --> 0000 0101
  ~   ~~~~ ~~~~
-6<-- 1111 1010

翻轉(zhuǎn)相當(dāng)于跟 -1做異或運(yùn)算,即~n = n ^ (-1)

十進(jìn)制     二進(jìn)制
5 --> 0000 0101
  ^        ^^^^
-1--> 1111 1111
  =        ||||
6 <-- 1111 1010

2.5 左移運(yùn)算(<<)

左移運(yùn)算是將二進(jìn)制數(shù)值整體向左邊移動(dòng)n個(gè)位置火惊,空出來的位置補(bǔ)0子库。

下面的 5 << 2示意如下:

十進(jìn)制     二進(jìn)制
5 --> 0000 0101
  <<    << 2
20<-- 0001 0100
  • 用于乘法計(jì)算
  5 * 7
= (101 * 111)_2
= (101 * (100 + 10 + 1))_2
= (101 * 100 + 101 * 10 + 101 * 1)_2
= 5 << 2 + 5 << 1 + 5 << 0
= (10100 + 01010 + 00101)_2
= 20 + 10 + 5
= 35

2.6 右移運(yùn)算(>>)

左移運(yùn)算是將二進(jìn)制數(shù)值整體向右邊移動(dòng)n個(gè)位置,空出來的位置補(bǔ)上符號位的數(shù)值矗晃。即正數(shù)補(bǔ)0仑嗅,負(fù)數(shù)補(bǔ)1。

下面的 5 >> 2张症、-5 >> 2示意如下:

十進(jìn)制     二進(jìn)制
5 --> 0000 0101
  >>    >> 2
1 <-- 0000 0001

十進(jìn)制     二進(jìn)制
-5--> 1111 1011
  >>    >> 2
-1<-- 1111 1110
  • 用于除法
  5 / 3
= 5 >> 2
= (101 / 100)_2
= (001)_2
= 1

2.7 位運(yùn)算實(shí)現(xiàn)加減乘除

def add(num1, num2):
    while num2 != 0:
        temp = num1 ^ num2
        num2 = (num1 & num2) << 1
        num1 = temp
    return min(max(-2147483648, num1), 2147483647)

def sub(num1, num2):
    return add(num1, add(~num2, 1))

def mul(num1, num2):
    sign = (num1 > 0) is (num2 > 0)

    if num1 < 0:
        num1 = add(~num1, 1)
    if num2 < 0:
        num2 = add(~num2, 1)

    result = 0
    while num2:
        if num2 & 1:
            result = add(result, num1)
        num1 = num1 << 1
        num2 = num2 >> 1

    if not sign:
        result = - result
    return result

def div(num1, num2):
    sign = (num1 > 0) is (num2 > 0)

    if num1 < 0:
        num1 = add(~num1, 1)
    if num2 < 0:
        num2 = add(~num2, 1)

    result = 0
    while (num1 >= num2):
        tmp, i = num2, 1
        n = 4
        while(num1 >= tmp):
            num1 -= tmp
            result += i

            tmp = tmp<<n
            i = i << n
        n = n >> 2

    if not sign:
        result = -result

    return min(max(-2147483648, result), 2147483647)

在早期版本中仓技,如Python2.7,整數(shù)的有int和long兩個(gè)類型俗他。int類型是一個(gè)固定位數(shù)的數(shù)脖捻;long則是一個(gè)理論上可以存儲無限大數(shù)的數(shù)據(jù)類型。當(dāng)數(shù)大到可能溢出時(shí)兆衅,為了避免溢出地沮,Python會把int轉(zhuǎn)化為long。

而Python3.x之后整數(shù)只有一個(gè)可以放任意大數(shù)的int了羡亩∧σ桑可是無論哪種,都是采用了特殊的方法實(shí)現(xiàn)了不會溢出的大整數(shù)畏铆。

在進(jìn)行負(fù)數(shù)的按位加法時(shí)雷袋,有可能發(fā)生在最高位還要向前進(jìn)一位的情形,正常來說辞居,這種進(jìn)位因?yàn)槌隽艘粋€(gè)int可以表示的最大位數(shù)楷怒,應(yīng)該舍去才能得到正確的結(jié)果。但在Python中因?yàn)樯鲜鲈驎a(chǎn)生一個(gè)不會溢出的大整數(shù)瓦灶,所以在進(jìn)行負(fù)數(shù)的按位加法時(shí)鸠删,結(jié)果會不斷變大。

這個(gè)問題在 Java贼陶、C刃泡、C++ 中不會存在,這也是Python效率低的一個(gè)原因每界。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末捅僵,一起剝皮案震驚了整個(gè)濱河市家卖,隨后出現(xiàn)的幾起案子眨层,更是在濱河造成了極大的恐慌,老刑警劉巖上荡,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件趴樱,死亡現(xiàn)場離奇詭異馒闷,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)叁征,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門纳账,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人捺疼,你說我怎么就攤上這事疏虫。” “怎么了啤呼?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵卧秘,是天一觀的道長。 經(jīng)常有香客問我官扣,道長翅敌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任惕蹄,我火速辦了婚禮蚯涮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘卖陵。我一直安慰自己遭顶,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布泪蔫。 她就那樣靜靜地躺著液肌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鸥滨。 梳的紋絲不亂的頭發(fā)上嗦哆,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機(jī)與錄音婿滓,去河邊找鬼老速。 笑死,一個(gè)胖子當(dāng)著我的面吹牛凸主,可吹牛的內(nèi)容都是我干的橘券。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼卿吐,長吁一口氣:“原來是場噩夢啊……” “哼旁舰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起嗡官,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤箭窜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后衍腥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體磺樱,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡纳猫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了竹捉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芜辕。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖块差,靈堂內(nèi)的尸體忽然破棺而出侵续,到底是詐尸還是另有隱情,我是刑警寧澤憨闰,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布询兴,位于F島的核電站,受9級特大地震影響起趾,放射性物質(zhì)發(fā)生泄漏诗舰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一训裆、第九天 我趴在偏房一處隱蔽的房頂上張望眶根。 院中可真熱鬧,春花似錦边琉、人聲如沸属百。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽族扰。三九已至,卻和暖如春定欧,著一層夾襖步出監(jiān)牢的瞬間渔呵,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工砍鸠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留扩氢,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓爷辱,卻偏偏與公主長得像录豺,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子饭弓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 網(wǎng)站亂碼問題我們會經(jīng)常碰到双饥,大多見于非英文的中文字符或其他字符亂碼,而且弟断,這類問題常常是因?yàn)榫幋a方式問題咏花,主要原因...
    波段頂?shù)?/span>閱讀 2,863評論 1 9
  • 進(jìn)制基本概念 什么是進(jìn)制?進(jìn)制是一種計(jì)數(shù)的方式,數(shù)值的表示形式 常見的進(jìn)制十進(jìn)制、二進(jìn)制夫嗓、八進(jìn)制迟螺、十六進(jìn)制 進(jìn)制書...
    極客江南閱讀 2,009評論 0 11
  • 運(yùn)算符是處理數(shù)據(jù)的基本方法冲秽,用來從現(xiàn)有的值得到新的值舍咖。JavaScript 提供了多種運(yùn)算符矩父,本章逐一介紹這些運(yùn)算...
    許先生__閱讀 604評論 0 3
  • 「WTF系列」深入Java中的位操作 關(guān)于WTF系列 引 學(xué)完本章節(jié)你將學(xué)會位的基礎(chǔ)概念與語法,并且還會一些騷操作...
    qiujuer閱讀 887評論 0 5
  • package Aswish; public class StaticDemo2 extends StaticFu...
    明明的故事閱讀 922評論 0 0