從零開始Python | Python 中的按位運算符 I 從零開始學python | Python 中的按位運算符 II

      網友投稿 1281 2025-03-31

      目錄

      Python 的按位運算符概述

      五分鐘了解二進制系統

      為什么使用二進制?

      二進制如何工作?

      計算機如何使用二進制

      按位邏輯運算符

      按位與

      按位或

      按位異或

      按位非

      按位移位運算符

      左移

      右移

      算術與邏輯移位

      二進制數表示

      無符號整數

      有符號整數

      浮點數字

      定點數

      Python中的整數

      實習整數

      定精度整數

      任意精度整數

      Python 中的位串

      將整數轉換為二進制

      將二進制轉換為整數

      模擬符號位

      查看二進制數據

      字節順序

      大端與小端

      本機字節序

      網絡字節順序

      位掩碼

      得到一點

      設置位

      取消設置位

      切換一點

      按位運算符重載

      內置數據類型

      第三方模塊

      自定義數據類型

      最不重要的位隱寫術

      密碼學與隱寫術

      位圖文件格式

      按位捉迷藏

      結論

      計算機將各種信息存儲為稱為位的二進制數字流。無論您是處理文本、圖像還是視頻,它們都歸結為 1 和 0。Python 的按位運算符讓您可以在最細粒度的級別上操作這些單獨的數據位。

      您可以使用按位運算符來實現壓縮、加密和錯誤檢測等算法,以及控制Raspberry Pi 項目或其他地方的物理設備。通常,Python 將您與具有高級抽象的底層位隔離開來。在實踐中,您更有可能發現按位運算符的重載風格。但是,當您以它們的原始形式與它們合作時,您會驚訝于它們的怪癖!

      在本教程中,您將學習如何:

      使用 Python按位運算符操作單個位

      以與平臺無關的方式讀取和寫入二進制數據

      使用位掩碼將信息打包到單個字節上

      在自定義數據類型中重載Python 按位運算符

      在數字圖像中隱藏秘密信息

      Python 的按位運算符概述

      Python 附帶了幾種不同類型的運算符,例如算術、邏輯和比較運算符。您可以將它們視為利用更緊湊的前綴和中綴語法的函數。

      注意:?Python 不包括后綴運算符,如C 中可用的增量 (?i++) 或減量 (?i--) 運算符。

      按位運算符在不同的編程語言中看起來幾乎相同:

      如您所見,它們用看起來很奇怪的符號而不是單詞來表示。這使它們在 Python 中脫穎而出,比您可能習慣看到的要少一些冗長。您可能無法僅通過查看它們來弄清楚它們的含義。

      注意:如果您來自另一種編程語言,例如Java,那么您會立即注意到 Python 缺少由三個大于號 (?)表示的無符號右移運算符>>>。

      這與 Python 在內部如何表示整數有關。由于 Python 中的整數可以有無數位,因此符號位沒有固定位置。事實上,Python 中根本沒有符號位!

      大多數按位運算符是binary,這意味著它們期望使用兩個操作數,通常稱為左操作數和右操作數。按位非 (?~) 是唯一的一元按位運算符,因為它只需要一個操作數。

      所有二元按位運算符都有一個相應的復合運算符來執行擴充賦值:

      這些是用于更新左操作數的速記符號。

      這就是 Python 的按位運算符語法的全部內容!現在,您已準備好仔細查看每個運算符,以了解它們最有用的地方以及如何使用它們。首先,在查看兩類按位運算符之前,您將快速復習一下二進制系統:按位邏輯運算符和按位移位運算符。

      五分鐘了解二進制系統

      在繼續之前,花點時間復習一下二進制系統的知識,這對于理解按位運算符至關重要。如果您已經對它感到滿意,請繼續并跳轉到下面的位邏輯運算符部分。

      為什么使用二進制?

      有無數種方法來表示數字。自古以來,人們就發展出不同的符號,如羅馬數字和埃及象形文字。大多數現代文明都使用位置符號,它高效、靈活,非常適合進行算術運算。

      任何位置系統的一個顯著特征是它的基數,它表示可用的位數。人們自然有利于基地十數字系統,也被稱為十進制系統,因為它與手指計數起著很好。

      另一方面,計算機將數據視為一組以二為基數的數字系統(通常稱為二進制系統)表示的數字。此類數字僅由兩位數字組成,即零和一。

      注意:在數學書籍中,數字文字的基數通常用略低于基線的下標表示,例如 42?10。

      例如,二進制數 10011100?2相當于十進制中的 156?10。因為十進制中有十個數字——從零到九——用十進制寫出相同的數字通常比用二進制寫出的數字少。

      注意:您不能僅通過查看給定數字的數字來判斷數字系統。

      例如,十進制數 101?10恰好只使用二進制數字。但它代表的值與其二進制對應物 101?2完全不同,后者相當于 5?10。

      二進制系統比十進制系統需要更多的存儲空間,但在硬件中實現起來要簡單得多。雖然您需要更多的積木,但它們更容易制作,而且種類較少。這就像將您的代碼分解為更模塊化和可重用的部分。

      然而,更重要的是,二進制系統非常適合將數字轉換為不同電壓電平的電子設備。由于電壓喜歡因各種噪聲而上下漂移,因此您需要在連續電壓之間保持足夠的距離。否則,信號可能會失真。

      通過僅采用兩種狀態,您可以使系統更加可靠和抗噪。或者,您可以升高電壓,但這也會增加功耗,這是您絕對想要避免的。

      二進制如何工作?

      想象一下,您只有兩個手指可以依靠。你可以數一個零、一個一和一個二。但是當你用完手指時,你需要記下你已經數到 2 的次數,然后重新開始,直到再次數到 2:

      每次你寫下另一對手指時,你還需要按 2 的冪對它們進行分組,這是系統的基礎。例如,要數到 13,您必須同時用兩個手指六次,然后再用一根手指。你的手指可以排列成一八、一四、一一。

      這些 2 的冪對應于二進制數中的數字位置,并準確地告訴您要打開哪些位。它們從右到左增長,從最低有效位開始,這決定了數字是偶數還是奇數。

      位置符號就像汽車中的里程表:一旦某個特定位置的數字達到其最大值(二進制系統中的 1),它就會滾動到零,然后向左移動。如果數字左側已經有一些,這可能會產生級聯效果。

      計算機如何使用二進制

      現在你知道了雙星系統的基本原理和為什么計算機使用它,你就可以學會如何,他們代表了它的數據。

      在以數字形式復制任何信息之前,您必須將其分解為數字,然后將它們轉換為二進制系統。例如,純文本可以被認為是一串字符。您可以為每個字符分配一個任意數字或選擇現有的字符編碼,例如ASCII、ISO-8859-1或UTF-8。

      在 Python 中,字符串表示為Unicode代碼點數組。要顯示它們的順序值,請調用ord()每個字符:

      >>> [ord(character) for character in "€uro"] [8364, 117, 114, 111]

      生成的數字唯一標識 Unicode 空間中的文本字符,但它們以十進制形式顯示。你想用二進制數字重寫它們:

      請注意,位長度(二進制數字的數量)在字符之間變化很大。歐元符號 (?€) 需要 14 位,而其余字符可以輕松容納 7 位。

      注意:以下是在 Python 中檢查任何整數的位長的方法:

      >>>

      >>> (42).bit_length() 6

      如果數字周圍沒有一對括號,它將被視為帶有小數點的浮點文字。

      可變位長是有問題的。例如,如果您將這些二進制數并排放置在光盤上,那么您最終會得到一長串沒有字符之間明確界限的位流:

      100000101011001110101111001011011112

      了解如何解釋此信息的一種方法是為所有字符指定固定長度的位模式。在現代計算中,最小的信息單位稱為八位字節或字節,由八位組成,可以存儲 256 個不同的值。

      您可以用前導零填充二進制代碼點以用字節表示它們:

      現在每個字符需要兩個字節,或 16 位。總的來說,您的原始文本大小幾乎翻了一番,但至少它的編碼可靠。

      您可以使用霍夫曼編碼為特定文本中的每個字符找到明確的位模式,或者使用更合適的字符編碼。例如,為了節省空間,UTF-8 特意使用拉丁字母而不是您不太可能在英文文本中找到的符號:

      >>>

      >>> len("€uro".encode("utf-8")) 6

      根據 UTF-8 標準編碼,整個文本占用六個字節。由于 UTF-8 是 ASCII 的超集,因此字母u、r和o各占一個字節,而歐元符號在此編碼中占用三個字節:

      >>> for char in "€uro": ... print(char, len(char.encode("utf-8"))) ... € 3 u 1 r 1 o 1

      從零開始學python | Python 中的按位運算符 I 從零開始學python | Python 中的按位運算符 II

      其他類型的信息可以與文本類似地數字化。光柵圖像由像素組成,每個像素都有將顏色強度表示為數字的通道。聲音波形包含與給定采樣間隔的氣壓相對應的數字。三維模型是根據由其頂點定義的幾何形狀構建的,等等。

      歸根結底,一切都是數字。

      按位邏輯運算符

      您可以使用按位運算符對單個位執行布爾邏輯。這是類似于使用邏輯運算符,如and,or和not,但有點水平。按位運算符和邏輯運算符之間的相似之處不止于此。

      可以使用按位運算符而不是邏輯運算符來計算布爾表達式,但通常不鼓勵這種過度使用。如果您對詳細信息感興趣,則可以展開下面的框以了解更多信息。

      使用位運算符計算布爾表達式顯示隱藏

      除非您有充分的理由并且知道自己在做什么,否則您應該僅將按位運算符用于控制位。否則很容易出錯。在大多數情況下,您需要將整數作為參數傳遞給按位運算符。

      按位與

      按位與運算符 (?&)對其操作數的相應位執行邏輯與操作。對于在兩個數字中占據相同位置的每一對位,只有當兩個位都打開時,它才返回一個:

      結果位模式是運算符參數的交集。它在兩個操作數都是 1 的位置打開了兩個位。在所有其他地方,至少有一個輸入具有零位。

      在算術上,這相當于兩個位值的乘積。您可以通過在每個索引i處乘以它們的位來計算數字a和b的按位與:

      這是一個具體的例子:

      一乘以一給出一,但任何乘以零的結果總是為零。或者,您可以取每對中兩位中的最小值。請注意,當操作數的位長不相等時,較短的操作數會自動在左側填充零。

      按位或

      按位 OR 運算符 (?|) 執行邏輯分離。對于每一對對應的位,如果其中至少一個被打開,它會返回一個:

      結果位模式是運算符參數的聯合。它打開了五個位,其中任何一個操作數都有一個 1。只有兩個零的組合在最終輸出中才會給出零。

      它背后的算術是位值的總和和乘積的組合。要計算數字a和b的按位或,您需要將以下公式應用于它們在每個索引i處的位:

      這是一個切實的例子:

      它幾乎就像兩位的總和,但被限制在較高端,因此它永遠不會超過 1 的值。您還可以取每對中兩位的最大值來獲得相同的結果。

      按位異或

      與按位AND、OR和NOT 不同,按位異或運算符 (?^) 在 Python 中沒有邏輯對應物。但是,您可以通過在現有運算符之上構建來模擬它:

      def xor(a, b): return (a and not b) or (not a and b)

      它評估兩個相互排斥的條件,并告訴您是否正好滿足其中之一。例如,一個人可以是未成年人也可以是成年人,但不能同時是兩者。相反,一個人不可能既不是未成年人也不是成年人。選擇是強制性的。

      名稱 XOR 代表“異或”,因為它對位對執行異或。換句話說,每個位對必須包含相反的位值才能產生一個:

      從視覺上看,這是運算符參數的對稱差異。結果中打開了三個位,其中兩個數字具有不同的位值。其余位置的位相互抵消,因為它們是相同的。

      與按位 OR 運算符類似,XOR 的算術涉及求和。然而,雖然按位 OR 將值鉗位為 1,但 XOR 運算符使用求和模 2將它們包裹起來:

      模數是兩個數字的函數——被除數和除數——執行除法并返回其余數。在 Python 中,有一個用百分號 (?)表示的內置模運算符%。

      再一次,您可以通過查看示例來確認公式:

      兩個 0 或兩個 1 的總和除以 2 會產生一個整數,因此結果的余數為零。但是,當您將兩個不同位值的總和除以2 時,您會得到一個余數為 1 的分數。XOR 運算符的一個更直接的公式是每對中兩個位的最大值和最小值之間的差。

      按位非

      最后一個按位邏輯運算符是按位非運算符 (?~),它只需要一個參數,使其成為唯一的一元按位運算符。它通過翻轉所有位對給定數字執行邏輯否定:

      反轉位是 1 的補碼,它將零變成 1,將 1 變成零。它可以在算術上表示為從 1 中減去單個位值:

      這是一個示例,顯示了之前使用的數字之一:

      雖然按位 NOT 運算符似乎是所有運算符中最直接的,但在 Python 中使用它時需要格外小心。到目前為止,您閱讀的所有內容都基于數字用無符號整數表示的假設。

      注意:無符號數據類型不允許您存儲負數,例如 -273,因為常規位模式中沒有用于符號的空間。嘗試這樣做會導致編譯錯誤、運行時異常或整數溢出,具體取決于所使用的語言。

      盡管有多種方法可以模擬無符號整數,但 Python 本身并不支持它們。這意味著無論您是否指定一個數字,所有數字都會附加一個隱式符號。這顯示當您對任何數字進行按位 NOT 操作時:

      >>> ~156 -157

      您得到的不是預期的 99?10,而是負值!一旦您了解了各種二進制數表示法,原因就會變得很清楚。目前,快速修復解決方案是利用按位 AND 運算符:

      >>>

      >>> ~156 & 255 99

      這是位掩碼的一個完美示例,您將在接下來的部分中對其進行探討。

      按位移位運算符

      按位移位運算符是另一種位操作工具。它們讓您可以移動位,這對于稍后創建位掩碼非常方便。過去,它們經常被用來提高某些數學運算的速度。

      左移

      按位左移運算符 (?<<) 將其第一個操作數的位向左移動其第二個操作數中指定的位數。它還負責插入足夠的零位以填充新位模式右邊緣出現的間隙:

      將單個位向左移動一個位置會使其值加倍。例如,該位將指示移位后的 4,而不是 2。將它向左移動兩個位置將使結果值翻四倍。當您將給定數字中的所有位相加時,您會注意到它也會隨著每個位置的移動而加倍:

      通常,向左移動位對應于將數字乘以2的冪,指數等于移動的位數:

      左移曾經是一種流行的優化技術,因為位移是一條指令,并且比指數或乘積的計算成本更低。然而,今天,編譯器和解釋器,包括 Python 的,非常有能力在幕后優化你的代碼。

      注意:不要在 Python 中使用位移運算符作為過早優化的手段。您不會看到執行速度的差異,但絕對會降低代碼的可讀性。

      在紙面上,左移產生的位模式會隨著您移位的位置而變長。由于 Python 處理整數的方式,一般來說這對 Python 也是如此。但是,在大多數實際情況下,您需要將位模式的長度限制為 8 的倍數,這是標準字節長度。

      例如,如果您正在處理單個字節,則將其向左移動應該丟棄超出其左邊界的所有位:

      這有點像通過固定長度的窗口查看無限的比特流。有一些技巧可以讓你在 Python 中做到這一點。例如,您可以使用按位 AND 運算符應用位掩碼:

      >>>

      >>> 39 << 3 312 >>> (39 << 3) & 255 56

      將 39?10向左移動三位會返回一個高于您可以存儲在單個字節上的最大值的數字。它需要九位,而一個字節只有八位。要砍掉左邊的一個額外位,您可以應用具有適當值的位掩碼。如果您想保留更多或更少的位,則需要相應地修改掩碼值。

      右移

      按位右移運算符 (?>>) 類似于左移運算符,但不是將位向左移動,而是將它們向右推指定的位數。最右邊的位總是被丟棄:

      每次向右移動一個位置,它的潛在價值就會減半。將相同的位向右移動兩位會產生原始值的四分之一,依此類推。當您將所有單個位相加時,您會看到相同的規則適用于它們所代表的數字:

      將諸如 157?10 之類的奇數減半會產生一個分數。為了擺脫它,自動向右移位運算符樓層的結果。它實際上與按 2 的冪進行的樓層劃分相同:

      同樣,指數對應于向右移動的位數。在 Python 中,您可以利用專用運算符來執行樓層劃分:

      >>>

      >>> 5 >> 1 # Bitwise right shift 2 >>> 5 // 2 # Floor division (integer division) 2 >>> 5 / 2 # Floating-point division 2.5

      按位右移運算符和地板除法運算符的工作方式相同,即使對于負數也是如此。但是,地板除法讓您可以選擇任何除數,而不僅僅是 2 的冪。使用按位右移是提高某些算術除法性能的常用方法。

      注意:您可能想知道當您用完要移位的位時會發生什么。例如,當您嘗試推入比數字中位數更多的位置時:

      >>>

      >>> 2 >> 5 0

      一旦沒有更多位打開,您就會被困在零值上。零除以任何東西將始終返回零。但是,當您右移負數時,事情會變得更加棘手,因為隱式符號位會妨礙您:

      >>>

      >>> -2 >> 5 -1

      經驗法則是,無論符號如何,結果都將與以 2 的某個冪進行地板除法相同。一個小的負分數的底總是負一,這就是你會得到的。繼續閱讀以獲得更詳細的解釋。

      就像左移運算符一樣,位模式在右移后改變其大小。雖然向右移動位會使二進制序列更短,但這通常無關緊要,因為您可以在不更改值的情況下在位序列前面放置任意數量的零。例如, 101?2與 0101?2相同,00000101?2也是如此,前提是您處理的是非負數。

      有時您會希望在右移后保留給定的位長以將其與另一個值對齊或適合某處。您可以通過應用位掩碼來做到這一點:

      它只雕刻出您感興趣的那些位,并在必要時用前導零填充位模式。

      Python 中負數的處理與傳統的按位移位方法略有不同。在下一節中,您將更詳細地研究這一點。

      算術與邏輯移位

      您可以將按位移位運算符進一步分類為算術和邏輯移位運算符。雖然 Python 只允許您進行算術移位,但了解其他編程語言如何實現按位移位運算符以避免混淆和意外是值得的。

      這種區別來自他們處理符號位的方式,符號位通常位于有符號二進制序列的最左邊緣。實際上,它僅與右移運算符相關,這會導致數字翻轉其符號,從而導致整數溢出。

      注意:?例如,Java和JavaScript使用附加的大于號 (?>>>)來區分邏輯右移運算符。由于左移運算符在兩種移位中的行為一致,因此這些語言沒有定義邏輯左移對應物。

      通常,打開的符號位表示負數,這有助于保持二進制序列的算術特性:

      從左邊看這兩個二進制序列,您可以看到它們的第一位攜帶符號信息,而其余部分由幅度位組成,這兩個數字相同。

      注意:具體的十進制值將取決于您決定如何以二進制表示有符號數。它因語言而異,在 Python 中變得更加復雜,因此您可以暫時忽略它。一旦你進入下面的二進制數表示部分,你就會有一個更好的畫面。

      邏輯右移,也稱為無符號右移或零填充右移,移動整個二進制序列,包括符號位,并用零填充左邊的結果間隙:

      請注意有關數字符號的信息是如何丟失的。不管原始符號如何,它總是會產生一個非負整數,因為符號位被零替換。只要您對數值不感興趣,邏輯右移在處理低級二進制數據時就很有用。

      但是,由于在大多數語言中,有符號二進制數通常存儲在固定長度的位序列上,因此它可以使結果環繞極值。您可以在交互式 Java Shell 工具中看到這一點:

      jshell> -100 >>> 1 $1 ==> 2147483598

      結果數字的符號從負變為正,但它也會溢出,最終非常接近 Java 的最大整數:

      jshell> Integer.MAX_VALUE $2 ==> 2147483647

      這個數字乍一看似乎是任意的,但它與 Java 為Integer數據類型分配的位數直接相關:

      jshell> Integer.toBinaryString(-100) $3 ==> "11111111111111111111111110011100"

      它采用32位來存儲有符號整數的二進制補碼表示。去掉符號位后,剩下 31 位,其最大十進制值等于 2?31?- 1 或 2147483647?10。

      另一方面,Python 存儲整數就好像有無數位可供您使用一樣。因此,在純 Python 中無法很好地定義邏輯右移運算符,因此語言中缺少它。不過,您仍然可以模擬它。

      這樣做的一種方法是利用通過內置模塊公開的C中可用的無符號數據類型ctypes:

      >>>

      >>> from ctypes import c_uint32 as unsigned_int32 >>> unsigned_int32(-100).value >> 1 2147483598

      它們讓您傳入一個負數,但不給符號位附加任何特殊含義。它被視為其余的幅度位。

      雖然 C 中只有幾種預定義的無符號整數類型,它們的位長不同,但您可以在 Python 中創建一個自定義函數來處理任意位長:

      >>> def logical_rshift(signed_integer, places, num_bits=32): ... unsigned_integer = signed_integer % (1 << num_bits) ... return unsigned_integer >> places ... >>> logical_rshift(-100, 1) 2147483598

      這會將有符號位序列轉換為無符號位序列,然后執行常規算術右移。

      但是,由于 Python 中的位序列長度不固定,因此它們實際上沒有符號位。此外,它們不像在 C 或 Java 中那樣使用傳統的二進制補碼表示。為了緩解這種情況,您可以利用模運算,這將保留正整數的原始位模式,同時適當地環繞負整數。

      算術右移 (?>>),有時稱為有符號右移運算符,通過在向右移動位之前復制其符號位來保持數字的符號:

      換句話說,它用符號位填充左邊的空白。結合有符號二進制的二進制補碼表示,這會產生算術上正確的值。無論數字是正數還是負數,算術右移都相當于地板除法。

      正如您即將發現的那樣,Python 并不總是以簡單的二進制補碼形式存儲整數。相反,它遵循自定義自適應策略,其工作方式類似于具有無限位數的符號大小。它在數字的內部表示和二進制補碼之間來回轉換數字,以模仿算術移位的標準行為。

      二進制數表示

      在使用按位求反 (?~) 和右移運算符 (?>>)時,您已經親身體驗到 Python 中缺少無符號數據類型。您已經看到有關在 Python 中存儲整數的不尋常方法的提示,這使得處理負數變得棘手。要有效地使用按位運算符,您需要了解二進制數字的各種表示形式。

      無符號整數

      在 C 等編程語言中,您可以選擇是使用給定數字類型的有符號還是無符號風格。當您確定永遠不需要處理負數時,無符號數據類型更合適。通過分配一個額外的位(否則將用作符號位),您實際上可以將可用值的范圍擴大一倍。

      通過在溢出發生之前增加最大限制,它還可以使事情變得更安全。但是,溢出只發生在固定位長的情況下,因此它們與沒有此類約束的 Python 無關。

      在 Python 中體驗無符號數字類型的最快方法是使用前面提到的ctypes模塊:

      >>> from ctypes import c_uint8 as unsigned_byte >>> unsigned_byte(-42).value 214

      由于此類整數中沒有符號位,因此它們的所有位都表示數字的大小。傳遞負數會強制 Python 重新解釋位模式,就好像它只有幅度位一樣。

      有符號整數

      一個數的符號只有兩種狀態。如果您暫時忽略零,那么它可以是正數或負數,這很好地轉換為二進制系統。然而,有幾種替代方法可以用二進制表示有符號整數,每種方法都有其優點和缺點。

      可能最直接的一個是sign-magnitude,它自然地建立在無符號整數之上。當二進制序列被解釋為符號大小時,最高有效位扮演符號位的角色,而其余位的工作方式與往常相同:

      最左邊位的零表示正 (?+) 數,一表示負 (?-) 數。請注意,符號位對符號幅度表示中的數字絕對值沒有貢獻。它的存在只是為了讓您翻轉剩余位的符號。

      為什么是最左邊的一點?

      它使位索引保持完整,這反過來又有助于保持用于計算二進制序列十進制值的位權重的向后兼容性。然而,并不是關于符號幅度的一切都那么好。

      注意:有符號整數的二進制表示只對固定長度的位序列有意義。否則,您無法分辨符號位在哪里。然而,在 Python 中,你可以用任意多的位來表示整數:

      >>>

      >>> f"{-5 & 0b1111:04b}" '1011' >>> f"{-5 & 0b11111111:08b}" '11111011'

      無論是四位還是八位,符號位總是會出現在最左邊的位置。

      可以在符號大小位模式中存儲的值范圍是對稱的。但這也意味著你最終有兩種表達零的方式:

      零在技術上沒有符號,但沒有辦法不在符號大小中包含一個。雖然在大多數情況下有一個模糊的零并不理想,但這并不是故事中最糟糕的部分。這種方法的最大缺點是繁瑣的二進制算術。

      當您將標準二進制算術應用于以符號大小存儲的數字時,它可能不會給您預期的結果。例如,將兩個大小相同但符號相反的數字相加不會使它們相消:

      42 和 -42 的總和不會產生零。此外,殘留比特有時可以從大小傳播到符號位,反轉符號并產生了意想不到的結果。

      為了解決這些問題,一些早期的計算機采用了補碼表示。這個想法是改變十進制數映射到特定二進制序列的方式,以便它們可以正確相加。要更深入地了解一個人的補語,您可以展開以下部分。

      盡管如此,現代計算機不使用補碼來表示整數,因為有一種更好的方法稱為二進制補碼。通過應用一個小的修改,您可以一次性消除雙零并簡化二進制算術。要更詳細地探索二進制補碼,您可以展開以下部分。

      補碼顯示隱藏

      有符號數表示還有其他一些變體,但它們并不那么受歡迎。

      浮點數字

      在IEEE 754標準定義了由所述的實數的二進制表示符號,指數和尾數位。在不涉及太多技術細節的情況下,您可以將其視為二進制數的科學記數法。小數點“浮動”以容納不同數量的有效數字,但它是一個二進制小數點。

      廣泛支持符合該標準的兩種數據類型:

      單精度:?1 個符號位、8 個指數位、23 個尾數位

      雙精度:?1 個符號位、11 個指數位、52 個尾數位

      Python 的float數據類型相當于雙精度類型。請注意,某些應用程序需要更多或更少的位。例如,OpenEXR 圖像格式利用半精度以合理的文件大小表示具有高動態顏色范圍的像素。

      數字 Pi (π) 在四舍五入到小數點后五位時具有以下單精度二進制表示:

      符號位的工作方式與整數類似,因此零表示正數。然而,對于指數和尾數,可以根據一些邊緣情況應用不同的規則。

      首先,您需要將它們從二進制轉換為十進制形式:

      指數:?128?10

      尾數:?2?-1?+ 2?-4?+ … + 2?-19?= 299261?10?/524288?10?≈ 0.570795?10

      指數存儲為無符號整數,但為了說明負值,它通常在單精度下具有等于 127?10的偏差。您需要減去它以恢復實際指數。

      尾數位代表一個分數,因此它們對應于 2 的負冪。此外,您需要向尾數添加一個,因為在這種特殊情況下,它假定小數點之前有一個隱式前導位。

      綜上所述,您可以得出以下公式將浮點二進制數轉換為十進制數:

      當您用變量替換上例中的實際值時,您將能夠破譯以單精度存儲的浮點數的位模式:

      就是這樣,假設 Pi 已四舍五入到小數點后五位。稍后您將學習如何以二進制顯示這些數字。

      定點數

      雖然浮點數非常適合工程用途,但由于精度有限,它們在貨幣計算中失敗。例如,一些以十進制表示法具有有限表示的數字在二進制中只有無限表示。這通常會導致舍入誤差,隨著時間的推移會累積:

      >>>

      >>> 0.1 + 0.2 0.30000000000000004

      在這種情況下,最好使用 Python 的decimal模塊,該模塊實現定點算術并允許您指定在給定位長度上放置小數點的位置。例如,您可以告訴它要保留多少位數字:

      >>> from decimal import Decimal, localcontext >>> with localcontext() as context: ... context.prec = 5 # Number of digits ... print(Decimal("123.456") * 1) ... 123.46

      但是,它包括所有數字,而不僅僅是小數。

      注意:如果您正在處理有理數,那么您可能有興趣查看fractions模塊,它是 Python 標準庫的一部分。

      如果您不能或不想使用定點數據類型,可靠地存儲貨幣值的一種直接方法是將金額縮放到最小單位,例如美分,并用整數表示它們。

      Python中的整數

      在過去的編程時代,計算機內存非常寶貴。因此,語言可以讓您非常精細地控制為數據分配多少字節。讓我們快速瀏覽一下 C 中的一些整數類型作為示例:

      這些值可能因平臺而異。但是,如此豐富的數字類型允許您在內存中緊湊地排列數據。請記住,這些甚至不包括無符號類型!

      另一方面是諸如 JavaScript 之類的語言,它們只有一種數字類型來統治它們。雖然這對初學者來說不太容易混淆,但代價是增加了內存消耗、降低了處理效率和降低了精度。

      在談論按位運算符時,必須了解 Python 如何處理整數。畢竟,您將主要使用這些運算符來處理整數。Python 中有幾種完全不同的整數表示形式,它們取決于它們的值。

      實習整數

      在CPython中,在-5非常小的整數10和256?10被拘留在全局高速緩存來獲得一些性能,因為在該范圍內被廣泛使用。在實踐中,無論何時引用這些值之一,它們是在解釋器啟動時創建的單例,Python 將始終提供相同的實例:

      >>>

      >>> a = 256 >>> b = 256 >>> a is b True >>> print(id(a), id(b), sep="\n") 94050914330336 94050914330336

      這兩個變量具有相同的標識,因為它們引用內存中完全相同的對象。這是典型的引用類型,但不是不可變的值,例如整數。但是,當超出緩存值的范圍時,Python 將在變量賦值期間開始創建不同的副本:

      >>> a = 257 >>> b = 257 >>> a is b False >>> print(id(a), id(b), sep="\n") 140282370939376 140282370939120

      盡管具有相同的值,但這些變量現在指向不同的對象。但不要讓那個愚弄你。Python 偶爾會在幕后介入并優化您的代碼。例如,它會緩存在同一行多次出現的數字,而不管其值如何:

      >>>

      >>> a = 257 >>> b = 257 >>> print(id(a), id(b), sep="\n") 140258768039856 140258768039728 >>> print(id(257), id(257), sep="\n") 140258768039760 140258768039760

      變量a和b是獨立的對象,因為它們駐留在不同的內存位置,而字面上使用的數字print()實際上是同一個對象。

      注意:?Interning 是CPython 解釋器的一個實現細節,在未來的版本中可能會發生變化,所以不要在你的程序中依賴它。

      有趣的是,Python 中也有類似的字符串實習機制,它適用于僅由 ASCII 字母組成的短文本。它允許通過內存地址或C 指針而不是單個字符串字符來比較它們的鍵,從而有助于加快字典查找速度。

      定精度整數

      您最有可能在 Python 中找到的整數將利用 Csigned long數據類型。它們在固定位數上使用經典的二進制補碼二進制表示。確切的位長取決于您的硬件平臺、操作系統和 Python 解釋器版本。

      現代計算機通常使用64 位體系結構,因此這將轉換為 -2?63和 2?63?- 1之間的十進制數。 您可以通過以下方式檢查 Python 中固定精度整數的最大值:

      >>>

      >>> import sys >>> sys.maxsize 9223372036854775807

      它超大!大約是我們銀河系中恒星數量的 900 萬倍,所以它應該足夠日常使用了。雖然您可以從unsigned longC中的類型中擠出的最大值更大,但在 10?19的數量級上,Python 中的整數沒有理論限制。為此,不適合固定長度位序列的數字在內存中的存儲方式不同。

      任意精度整數

      您還記得 2012 年成為全球熱門的流行 K-pop 歌曲“江南 Style”嗎?在YouTube的視頻是第一個打破十億意見。不久之后,觀看視頻的人太多了,以至于觀看計數器都溢出了。YouTube別無選擇,只能將他們的計數器從 32 位有符號整數升級到 64 位。

      這可能會為視圖計數器提供足夠的空間,但在現實生活中,尤其是在科學世界中,還有更大的數字并不少見。盡管如此,Python 可以毫不費力地處理它們:

      >>>

      >>> from math import factorial >>> factorial(42) 1405006117752879898543142606244511569936384000000000

      這個數字有五十二位十進制數字。用傳統方法用二進制表示它至少需要 170 位:

      >>>

      >>> factorial(42).bit_length() 170

      由于它們遠遠超過了任何 C 類型必須提供的限制,因此這些天文數字被轉換為符號幅度位置系統,其基數為 2?30。是的,你沒看錯。雖然你有十個手指,但 Python 有超過十億個!

      同樣,這可能會因您當前使用的平臺而異。如有疑問,您可以仔細檢查:

      >>> import sys >>> sys.int_info sys.int_info(bits_per_digit=30, sizeof_digit=4)

      這將告訴您每個數字使用多少位以及底層 C 結構的字節大小。要在 Python 2 中獲得相同的命名元組,您可以改為引用sys.long_info屬性。

      雖然固定精度和任意精度整數之間的這種轉換在 Python 3 的幕后無縫完成,但有一段時間事情變得更加明確。有關更多信息,您可以展開下面的框。

      int而long在Python 2顯示隱藏

      這種表示消除了整數溢出錯誤并給出了無限位長的錯覺,但它需要更多的內存。此外,執行bignum 算法比固定精度更慢,因為它不能在沒有中間仿真層的情況下直接在硬件中運行。

      另一個挑戰是在替代整數類型之間保持按位運算符的一致行為,這對于處理符號位至關重要。回想一下,Python 中的固定精度整數使用 C 中的標準二進制補碼表示,而大整數使用符號大小。

      為了減輕這種差異,Python 會為您進行必要的二進制轉換。在應用按位運算符之前和之后,它可能會改變數字的表示方式。這是來自CPython源代碼的相關注釋,它更詳細地解釋了這一點:

      負數的按位運算就像在二進制補碼表示上一樣。因此,將參數從符號大小轉換為二進制補碼,并在最后將結果轉換回符號大小。(來源)

      換句話說,當您對負數應用按位運算符時,負數被視為二進制補碼位序列,即使結果將以符號大小形式呈現給您。不過,有一些方法可以模擬Python 中的符號位和一些無符號類型。

      Python 中的位串

      歡迎您在本文的其余部分使用筆和紙。它甚至可以作為一個很好的鍛煉!但是,在某些時候,您需要驗證您的二進制序列或位字符串是否與 Python 中的預期數字相對應。就是這樣。

      轉換int為二進制

      要在 Python 中顯示組成整數的位,您可以打印格式化的字符串文字,它可以讓您選擇指定要顯示的前導零的數量:

      >>>

      >>> print(f"{42:b}") # Print 42 in binary 101010 >>> print(f"{42:032b}") # Print 42 in binary on 32 zero-padded digits 00000000000000000000000000101010

      或者,您可以bin()使用號碼作為參數調用:

      >>> bin(42) '0b101010'

      這個全局內置函數返回一個由二進制文字組成的字符串,它以前綴開頭,0b后跟一和零。它始終顯示沒有前導零的最小位數。

      您也可以在代碼中逐字使用此類文字:

      >>>

      >>> age = 0b101010 >>> print(age) 42

      Python 中可用的其他整數文字是十六進制和八進制文字,您可以分別使用hex()和oct()函數獲取它們:

      >>> hex(42) '0x2a' >>> oct(42) '0o52'

      請注意十六進制系統如何利用字母A直通F來擴充可用數字集。其他編程語言中的八進制文字通常以純零作為前綴,這可能會造成混淆。Python 明確禁止此類文字以避免出錯:

      >>>

      >>> 052 File "", line 1 SyntaxError: leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers

      您可以使用任何提到的整數文字以不同的方式表達相同的值:

      >>> 42 == 0b101010 == 0x2a == 0o52 True

      選擇在上下文中最有意義的一項。例如,習慣上用十六進制表示法來表示位掩碼。另一方面,現在很少看到八進制文字。

      Python 中的所有數字文字都不區分大小寫,因此您可以使用小寫或大寫字母作為前綴:

      >>>

      >>> 0b101 == 0B101 True

      這也適用于使用科學記數法的浮點數文字以及復數文字。

      將二進制轉換為?int

      準備好位字符串后,您可以通過利用二進制文字來獲取其十進制表示:

      >>>

      >>> 0b101010 42

      這是在交互式 Python 解釋器中工作時進行轉換的一種快速方法。不幸的是,它不會讓您轉換在運行時合成的位序列,因為所有文字都需要在源代碼中進行硬編碼。

      注意:您可能會想用 來評估Python 代碼eval("0b101010"),但這是一種危及程序安全的簡單方法,所以不要這樣做!

      int()在動態生成的位串的情況下,使用兩個參數調用會更好:

      >>>

      >>> int("101010", 2) 42 >>> int("cafe", 16) 51966

      第一個參數是一串數字,而第二個參數確定數字系統的基數。與二進制文字不同,字符串可以來自任何地方,甚至是用戶在鍵盤上打字。要更深入地了解int(),您可以展開下面的框。

      的其他用途?int()顯示隱藏

      到現在為止還挺好。但是負數呢?

      模擬符號位

      當你調用bin()一個負整數時,它只是在從相應的正值獲得的位串前面加上減號:

      >>>

      >>> print(bin(-42), bin(42), sep="\n ") -0b101010 0b101010

      更改數字的符號不會影響 Python 中的底層位串。相反,在將位串轉換為十進制形式時,允許在位串前加上減號:

      >>> int("-101010", 2) -42

      這在 Python 中是有意義的,因為在內部,它不使用符號位。您可以將 Python 中整數的符號視為與模數分開存儲的一條信息。

      但是,有一些變通方法可以讓您模擬包含符號位的固定長度位序列:

      位掩碼

      模運算 (?%)

      ctypes?模塊

      array?模塊

      struct?模塊

      您從前面的部分知道,為了確保數字的某個位長度,您可以使用漂亮的位掩碼。例如,要保留一個字節,您可以使用恰好由八個開啟位組成的掩碼:

      >>> mask = 0b11111111 # Same as 0xff or 255 >>> bin(-42 & mask) '0b11010110'

      掩碼強制 Python 暫時將數字的表示從符號幅度更改為二進制補碼,然后再返回。如果您忘記了結果二進制文字的十進制值(等于 214?10 ),那么它將以二進制補碼形式表示 -42?10。最左邊的位將是符號位。

      或者,您可以利用之前使用的模運算來模擬 Python 中的邏輯右移:

      >>> bin(-42 % (1 << 8)) # Give me eight bits '0b11010110'

      如果這對您的口味來說看起來過于復雜,那么您可以使用標準庫中的一個模塊來更清楚地表達相同的意圖。例如,使用ctypes將產生相同的效果:

      >>>

      >>> from ctypes import c_uint8 as unsigned_byte >>> bin(unsigned_byte(-42).value) '0b11010110'

      您以前見過它,但作為提醒,它會搭載 C 中的無符號整數類型。

      另一個可用于 Python 中此類轉換的標準模塊是array模塊。它定義了一個類似于a 的數據結構,list但只允許保存相同數字類型的元素。聲明數組時,需要用對應的字母在前面指明其類型:

      >>> from array import array >>> signed = array("b", [-42, 42]) >>> unsigned = array("B") >>> unsigned.frombytes(signed.tobytes()) >>> unsigned array('B', [214, 42]) >>> bin(unsigned[0]) '0b11010110' >>> bin(unsigned[1]) '0b101010'

      例如,"b"代表一個 8 位有符號字節,而"B"代表它的無符號等效字節。還有一些其他預定義類型,例如帶符號的 16 位整數或 32 位浮點數。

      在這兩個數組之間復制原始字節會改變位的解釋方式。然而,它需要兩倍的內存量,這是相當浪費的。要執行這樣的位重寫,您可以依賴struct模塊,它使用一組類似的格式字符進行類型聲明:

      >>>

      >>> from struct import pack, unpack >>> unpack("BB", pack("bb", -42, 42)) (214, 42) >>> bin(214) '0b11010110'

      打包允許您根據給定的 C 數據類型說明符將對象放置在內存中。它返回一個只讀bytes()對象,其中包含結果內存塊的原始字節。稍后,您可以使用一組不同的類型代碼讀回這些字節,以更改它們被轉換為 Python 對象的方式。

      到目前為止,您已經使用了不同的技術來獲得以二進制補碼表示的整數的固定長度位串。如果你想將這些類型的位序列轉換回 Python 整數,那么你可以試試這個函數:

      def from_twos_complement(bit_string, num_bits=32): unsigned = int(bit_string, 2) sign_mask = 1 << (num_bits - 1) # For example 0b100000000 bits_mask = sign_mask - 1 # For example 0b011111111 return (unsigned & bits_mask) - (unsigned & sign_mask)

      該函數接受由二進制數字組成的字符串。首先,它將數字轉換為普通的無符號整數,不考慮符號位。接下來,它使用兩個位掩碼來提取符號位和幅度位,其位置取決于指定的位長。最后,它使用常規算術將它們組合起來,知道與符號位關聯的值是負數。

      您可以針對前面示例中可靠的舊位字符串進行嘗試:

      >>> int("11010110", 2) 214 >>> from_twos_complement("11010110") 214 >>> from_twos_complement("11010110", num_bits=8) -42

      Pythonint()將所有位視為量級,因此沒有任何意外。但是,默認情況下,這個新函數假定一個 32 位長的字符串,這意味著對于較短的字符串,符號位隱式等于 0。當您請求與您的位字符串匹配的位長度時,您將獲得預期的結果。

      雖然在大多數情況下整數是最適合使用按位運算符的數據類型,但有時您需要提取和操作結構化二進制數據的片段,例如圖像像素。該array和struct在這個主題模塊暫時觸摸,所以你會更詳細地探索它旁邊。

      從零開始學python | Python 中的按位運算符 II

      Python 面向對象編程

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:怎么同一個表格里不同sheet表相加(不同sheet數據引用到同一張表格)
      下一篇:【云駐共創】智慧城市·科技惠民新高度
      相關文章
      亚洲成a人片在线网站| 婷婷国产偷v国产偷v亚洲| 亚洲精品国产首次亮相| 亚洲一区二区三区免费观看| 亚洲色成人网一二三区| 亚洲高清无在码在线无弹窗| 91久久亚洲国产成人精品性色 | 亚洲AV无码成人专区| 亚洲成a人片在线观看中文!!!| 亚洲欧洲自拍拍偷午夜色| 亚洲精品免费在线视频| 亚洲大香伊人蕉在人依线| 久久亚洲精品中文字幕| 亚洲色偷偷av男人的天堂| 亚洲国语在线视频手机在线| 亚洲人成电影网站| 久久久国产亚洲精品| 久久亚洲欧美国产精品| 亚洲成av人片一区二区三区| 久99精品视频在线观看婷亚洲片国产一区一级在线 | 亚洲乱理伦片在线观看中字| 亚洲人AV在线无码影院观看| 亚洲爆乳无码专区www| 国产成人精品亚洲| 中文字幕不卡亚洲| 亚洲成在人线av| 久久久亚洲裙底偷窥综合| wwwxxx亚洲| 日韩色视频一区二区三区亚洲 | 亚洲美免无码中文字幕在线| 亚洲区精品久久一区二区三区| 亚洲国产精品免费观看| 黑人粗长大战亚洲女2021国产精品成人免费视频 | 亚洲视频在线观看网站| 亚洲伦理一二三四| 亚洲精品无AMM毛片| 亚洲第一网站男人都懂| 久久亚洲高清观看| 亚洲黑人嫩小videos| 亚洲伦理中文字幕| 在线观看亚洲电影|