Cách đặt bit trong Python

Máy tính lưu trữ tất cả các loại thông tin dưới dạng dòng các chữ số nhị phân được gọi là bit. Cho dù bạn đang làm việc với văn bản, hình ảnh hay video, tất cả chúng đều biến thành số một và số không. Toán tử bitwise của Python cho phép bạn thao tác các bit dữ liệu riêng lẻ đó ở mức chi tiết nhất

Bạn có thể sử dụng toán tử bitwise để thực hiện các thuật toán như nén, mã hóa và phát hiện lỗi cũng như để kiểm soát các thiết bị vật lý trong dự án Raspberry Pi của bạn hoặc ở nơi khác. Thông thường, Python cô lập bạn khỏi các bit cơ bản với mức độ trừu tượng cao. Bạn có nhiều khả năng tìm thấy hương vị quá tải của các toán tử bitwise trong thực tế. Nhưng khi bạn làm việc với chúng ở dạng ban đầu, bạn sẽ ngạc nhiên bởi những điều kỳ quặc của chúng

Trong hướng dẫn này, bạn sẽ học cách

  • Sử dụng các toán tử bitwise Python để thao tác các bit riêng lẻ
  • Đọc và ghi dữ liệu nhị phân theo cách bất khả tri trên nền tảng
  • Sử dụng bitmasks để đóng gói thông tin trên một byte đơn
  • Quá tải toán tử bitwise Python trong các loại dữ liệu tùy chỉnh
  • Ẩn tin nhắn bí mật trong hình ảnh kỹ thuật số

Để lấy mã nguồn hoàn chỉnh của ví dụ thủy ấn kỹ thuật số và để trích xuất một điều bí mật ẩn trong một hình ảnh, hãy nhấp vào liên kết bên dưới

Lấy mã nguồn. Nhấp vào đây để lấy mã nguồn mà bạn sẽ sử dụng để tìm hiểu về các toán tử bitwise của Python trong hướng dẫn này

Tổng quan về toán tử Bitwise của Python

Python đi kèm với một số loại toán tử khác nhau, chẳng hạn như toán tử số học, logic và so sánh. Bạn có thể coi chúng như các hàm tận dụng cú pháp tiền tố và trung tố nhỏ gọn hơn

Ghi chú. Python không bao gồm các toán tử hậu tố như toán tử tăng [_______0_______9] hoặc giảm [

>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
0] có sẵn trong C

Các toán tử bitwise trông gần như giống nhau trên các ngôn ngữ lập trình khác nhau

OperatorExampleMeaning

>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
1
>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
2Bitwise AND
>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
3
>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
4Bitwise OR
>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
5
>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
6Bitwise XOR [exclusive OR]
>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
7
>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
8Bitwise NOT
>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
9
>>> [42].bit_length[]
6
00Bitwise left shift
>>> [42].bit_length[]
6
01
>>> [42].bit_length[]
6
02Bitwise right shift

Như bạn có thể thấy, chúng được biểu thị bằng các ký hiệu lạ thay vì các từ. Điều này làm cho chúng nổi bật trong Python vì ít dài dòng hơn bạn có thể thấy. Bạn có thể sẽ không thể hiểu được ý nghĩa của chúng chỉ bằng cách nhìn vào chúng

Ghi chú. Nếu bạn đến từ một ngôn ngữ lập trình khác chẳng hạn như Java, thì bạn sẽ nhận thấy ngay rằng Python thiếu toán tử dịch chuyển phải không dấu được biểu thị bằng ba dấu lớn hơn [

>>> [42].bit_length[]
6
03]

Điều này liên quan đến cách Python nội bộ. Vì các số nguyên trong Python có thể có vô số bit nên bit dấu không có vị trí cố định. Trên thực tế, không có chút dấu hiệu nào trong Python

Hầu hết các toán tử bitwise là nhị phân, có nghĩa là chúng mong đợi hai toán hạng hoạt động cùng, thường được gọi là toán hạng bên trái và toán hạng bên phải. Bitwise NOT [

>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
7] là toán tử bitwise đơn nguyên duy nhất vì nó chỉ mong đợi một toán hạng

Tất cả các toán tử bitwise nhị phân đều có toán tử ghép tương ứng thực hiện phép gán tăng cường

OperatorExampleEquivalent to

>>> [42].bit_length[]
6
05
>>> [42].bit_length[]
6
06
>>> [42].bit_length[]
6
07
>>> [42].bit_length[]
6
08
>>> [42].bit_length[]
6
09
>>> [42].bit_length[]
6
10
>>> [42].bit_length[]
6
11
>>> [42].bit_length[]
6
12
>>> [42].bit_length[]
6
13
>>> [42].bit_length[]
6
14
>>> [42].bit_length[]
6
15
>>> [42].bit_length[]
6
16
>>> [42].bit_length[]
6
17
>>> [42].bit_length[]
6
18
>>> [42].bit_length[]
6
19

Đây là các ký hiệu tốc ký để cập nhật toán hạng bên trái tại chỗ

Đó là tất cả những gì có trong cú pháp toán tử bitwise của Python. Bây giờ bạn đã sẵn sàng xem xét kỹ hơn từng toán tử để hiểu chúng hữu ích nhất ở đâu và cách bạn có thể sử dụng chúng. Trước tiên, bạn sẽ được xem lại nhanh về hệ thống nhị phân trước khi xem xét hai loại toán tử bitwise. toán tử logic theo bit và toán tử dịch chuyển theo bit

Loại bỏ các quảng cáo

Hệ thống nhị phân trong năm phút

Trước khi tiếp tục, hãy dành một chút thời gian để củng cố kiến ​​thức của bạn về hệ thống nhị phân, đây là điều cần thiết để hiểu các toán tử bitwise. Nếu bạn đã cảm thấy thoải mái với nó, hãy tiếp tục và chuyển sang phần bên dưới

Tại sao sử dụng nhị phân?

Có vô số cách biểu diễn số. Từ thời cổ đại, con người đã phát triển các ký hiệu khác nhau, chẳng hạn như chữ số La Mã và chữ tượng hình Ai Cập. Hầu hết các nền văn minh hiện đại sử dụng ký hiệu vị trí, hiệu quả, linh hoạt và rất phù hợp để thực hiện số học

Một tính năng đáng chú ý của bất kỳ hệ thống vị trí nào là cơ sở của nó, đại diện cho số chữ số có sẵn. Mọi người thường thích hệ đếm cơ số 10, còn được gọi là hệ thập phân, bởi vì nó hoạt động tốt với việc đếm trên đầu ngón tay

Mặt khác, máy tính coi dữ liệu là một loạt các số được biểu thị trong hệ thống cơ số hai chữ số, thường được gọi là hệ thống nhị phân. Những số như vậy bao gồm chỉ có hai chữ số, số không và một

Ghi chú. Trong sách toán, cơ sở của một chữ số thường được biểu thị bằng một chỉ số xuất hiện bên dưới đường cơ sở một chút, chẳng hạn như 4210

Ví dụ: số nhị phân 100111002 tương đương với 15610 trong hệ cơ số mười. Vì có mười chữ số trong hệ thập phân—từ 0 đến 9—nên thường cần ít chữ số hơn để viết cùng một số trong cơ số mười so với cơ số hai

Ghi chú. Bạn không thể biết một hệ thống số chỉ bằng cách nhìn vào các chữ số của một số nhất định

Ví dụ: số thập phân 10110 chỉ sử dụng các chữ số nhị phân. Nhưng nó đại diện cho một giá trị hoàn toàn khác so với đối tác nhị phân của nó, 1012, tương đương với 510

Hệ thống nhị phân yêu cầu nhiều không gian lưu trữ hơn hệ thống thập phân nhưng ít phức tạp hơn nhiều khi triển khai trong phần cứng. Mặc dù bạn cần nhiều khối xây dựng hơn nhưng chúng dễ tạo hơn và có ít loại hơn. Điều đó giống như chia mã của bạn thành nhiều phần mô-đun hơn và có thể tái sử dụng

Tuy nhiên, quan trọng hơn, hệ thống nhị phân hoàn hảo cho các thiết bị điện tử, giúp dịch các chữ số thành các mức điện áp khác nhau. Vì điện áp thích dao động lên xuống do nhiều loại nhiễu khác nhau, nên bạn muốn giữ khoảng cách vừa đủ giữa các điện áp liên tiếp. Nếu không, tín hiệu có thể bị méo

Bằng cách chỉ sử dụng hai trạng thái, bạn làm cho hệ thống trở nên đáng tin cậy hơn và chống lại tiếng ồn. Ngoài ra, bạn có thể tăng điện áp, nhưng điều đó cũng sẽ làm tăng mức tiêu thụ điện năng, điều mà bạn chắc chắn muốn tránh

Nhị phân hoạt động như thế nào?

Hãy tưởng tượng trong một khoảnh khắc rằng bạn chỉ có hai ngón tay để đếm. Bạn có thể đếm một số không, một và hai. Nhưng khi bạn hết ngón tay, bạn cần ghi lại số lần bạn đã đếm đến hai và sau đó bắt đầu lại cho đến khi bạn đếm lại hai

DecimalFingersEightsFoursTwosOnesBinary010✊000002110☝️000112210✌️0010102310✌️+☝️0011112410✌️✌️01001002510✌️✌️+☝️01011012610✌️✌️+✌️01101102710✌️✌️+✌️+☝️01111112810✌️✌️✌️✌️100010002910✌️✌️✌️✌️+☝️1001100121010✌️✌️✌️✌️+✌️1010101021110

Mỗi lần bạn viết ra một cặp ngón tay khác, bạn cũng cần nhóm chúng theo lũy thừa của hai, đây là cơ sở của hệ thống. Ví dụ: để đếm đến mười ba, bạn sẽ phải sử dụng cả hai ngón tay của mình sáu lần rồi sử dụng một ngón tay nữa. Các ngón tay của bạn có thể được sắp xếp thành một tám, một bốn và một một

Những lũy ​​thừa của hai tương ứng với các vị trí chữ số trong một số nhị phân và cho bạn biết chính xác bit nào sẽ bật. Chúng phát triển từ phải sang trái, bắt đầu từ bit ít quan trọng nhất, xác định xem số đó là chẵn hay lẻ

Ký hiệu vị trí giống như đồng hồ đo quãng đường trong ô tô của bạn. Khi một chữ số ở một vị trí cụ thể đạt đến giá trị tối đa của nó, là một trong hệ thống nhị phân, nó sẽ chuyển sang 0 và một chữ số sẽ chuyển sang bên trái. Điều này có thể có hiệu ứng xếp tầng nếu đã có một số chữ số ở bên trái của chữ số

Cách máy tính sử dụng nhị phân

Bây giờ bạn đã biết các nguyên tắc cơ bản của hệ thống nhị phân và tại sao máy tính sử dụng nó, bạn đã sẵn sàng tìm hiểu cách chúng biểu diễn dữ liệu với nó

Trước khi bất kỳ phần thông tin nào có thể được sao chép ở dạng kỹ thuật số, bạn phải chia nhỏ thông tin đó thành các số và sau đó chuyển đổi chúng sang hệ thống nhị phân. Ví dụ: văn bản thuần túy có thể được coi là một chuỗi ký tự. Bạn có thể gán một số tùy ý cho mỗi ký tự hoặc chọn mã hóa ký tự hiện có, chẳng hạn như ASCII, ISO-8859-1 hoặc UTF-8

Trong Python, các chuỗi được biểu diễn dưới dạng mảng các điểm mã Unicode. Để tiết lộ các giá trị thứ tự của chúng, hãy gọi

>>> [42].bit_length[]
6
20 trên mỗi ký tự

>>>

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

Các số kết quả xác định duy nhất các ký tự văn bản trong không gian Unicode, nhưng chúng được hiển thị ở dạng thập phân. Bạn muốn viết lại chúng bằng các chữ số nhị phân

Ký tự Điểm mã thập phân Điểm mã nhị phân€836410100000101011002u1171011101012r1141011100102o1111011011112

Lưu ý rằng độ dài bit, là số chữ số nhị phân, thay đổi rất nhiều giữa các ký tự. Dấu hiệu đồng euro [

>>> [42].bit_length[]
6
21] yêu cầu mười bốn bit, trong khi các ký tự còn lại có thể vừa với bảy bit

Ghi chú. Đây là cách bạn có thể kiểm tra độ dài bit của bất kỳ số nguyên nào trong Python

>>>

>>> [42].bit_length[]
6

Nếu không có một cặp dấu ngoặc đơn xung quanh số, nó sẽ được coi là một chữ số dấu phẩy động với dấu thập phân

Độ dài bit thay đổi có vấn đề. Ví dụ: nếu bạn đặt các số nhị phân đó cạnh nhau trên một đĩa quang, thì bạn sẽ có một dòng dài các bit không có ranh giới rõ ràng giữa các ký tự

100000101011001110101111001011011112

Một cách để biết cách diễn giải thông tin này là chỉ định các mẫu bit có độ dài cố định cho tất cả các ký tự. Trong điện toán hiện đại, đơn vị thông tin nhỏ nhất, được gọi là octet hoặc byte, bao gồm 8 bit có thể lưu trữ 256 giá trị riêng biệt

Bạn có thể đệm các điểm mã nhị phân của mình bằng các số 0 đứng đầu để biểu thị chúng dưới dạng byte

Ký tự Điểm mã thập phân Điểm mã nhị phân€83641000100000 101011002u1171000000000 011101012r1141000000000 011100102o1111000000000 011011112

Bây giờ mỗi ký tự chiếm hai byte hoặc 16 bit. Tổng cộng, văn bản gốc của bạn có kích thước gần gấp đôi, nhưng ít nhất nó được mã hóa một cách đáng tin cậy

Bạn có thể sử dụng mã hóa Huffman để tìm các mẫu bit rõ ràng cho mọi ký tự trong một văn bản cụ thể hoặc sử dụng mã hóa ký tự phù hợp hơn. Ví dụ: để tiết kiệm dung lượng, UTF-8 cố ý ưu tiên các chữ cái Latinh hơn các ký hiệu mà bạn ít có khả năng tìm thấy trong văn bản tiếng Anh

>>>

>>> len["€uro".encode["utf-8"]]
6

Được mã hóa theo tiêu chuẩn UTF-8, toàn bộ văn bản chiếm 6 byte. Vì UTF-8 là một siêu tập hợp của ASCII, nên các chữ cái

>>> [42].bit_length[]
6
22,
>>> [42].bit_length[]
6
23 và
>>> [42].bit_length[]
6
24 chiếm một byte mỗi ký tự, trong khi ký hiệu euro chiếm ba byte trong mã hóa này

>>>

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

Các loại thông tin khác có thể được số hóa tương tự như văn bản. Hình ảnh raster được tạo thành từ các pixel, với mỗi pixel có các kênh biểu thị cường độ màu dưới dạng số. Dạng sóng âm thanh chứa các số tương ứng với áp suất không khí tại một khoảng thời gian lấy mẫu nhất định. Các mô hình ba chiều được xây dựng từ các hình dạng hình học được xác định bởi các đỉnh của chúng, v.v.

Vào cuối ngày, mọi thứ đều là một con số

Loại bỏ các quảng cáo

Toán tử logic bitwise

Bạn có thể sử dụng các toán tử bitwise để thực hiện trên các bit riêng lẻ. Điều đó tương tự với việc sử dụng các toán tử logic chẳng hạn như

>>> [42].bit_length[]
6
25,
>>> [42].bit_length[]
6
26 và
>>> [42].bit_length[]
6
27, nhưng ở mức bit. Sự tương đồng giữa các toán tử bitwise và logic vượt xa điều đó

Có thể đánh giá các biểu thức Boolean bằng các toán tử bit thay vì các toán tử logic, nhưng việc sử dụng quá mức như vậy thường không được khuyến khích. Nếu bạn quan tâm đến chi tiết, thì bạn có thể mở rộng hộp bên dưới để tìm hiểu thêm

Đánh giá biểu thức Boolean bằng toán tử bitwiseHiển thị/Ẩn

Cách thông thường để chỉ định các biểu thức Boolean ghép trong Python là sử dụng các toán tử logic kết nối các vị từ liền kề, như thế này

if age >= 18 and not is_self_excluded:
    print["You can gamble"]

Tại đây, bạn kiểm tra xem người dùng có đủ mười tám tuổi trở lên hay không và liệu họ có chọn không tham gia cờ bạc hay không. Bạn có thể viết lại điều kiện đó bằng toán tử bitwise

if age >= 18 & ~is_self_excluded:
    print["You can gamble"]

Mặc dù biểu thức này đúng về mặt cú pháp, nhưng có một vài vấn đề với nó. Đầu tiên, nó được cho là ít đọc hơn. Thứ hai, nó không hoạt động như mong đợi đối với tất cả các nhóm dữ liệu. Bạn có thể chứng minh điều đó bằng cách chọn các giá trị toán hạng cụ thể

>>>

>>> age = 18
>>> is_self_excluded = True
>>> age >= 18 & ~is_self_excluded  # Bitwise logical operators
True
>>> age >= 18 and not is_self_excluded  # Logical operators
False

Biểu thức được tạo bởi các toán tử bit có giá trị là

>>> [42].bit_length[]
6
28, trong khi biểu thức tương tự được tạo từ các toán tử logic có giá trị là
>>> [42].bit_length[]
6
29. Đó là bởi vì các toán tử bitwise tiếp quản các toán tử so sánh, thay đổi cách diễn giải toàn bộ biểu thức

>>>

>>> age >= [18 & ~is_self_excluded]
True

Như thể ai đó đặt dấu ngoặc đơn ẩn xung quanh các toán hạng sai. Để khắc phục điều này, bạn có thể đặt dấu ngoặc đơn rõ ràng, điều này sẽ thực thi đúng thứ tự đánh giá

>>>

>>> [age >= 18] & ~is_self_excluded
0

Tuy nhiên, bạn không còn nhận được kết quả Boolean nữa. Các toán tử bitwise của Python được thiết kế chủ yếu để hoạt động với các số nguyên, vì vậy các toán hạng của chúng sẽ tự động được truyền nếu cần. Điều này có thể không phải lúc nào cũng có thể, mặc dù

Mặc dù bạn có thể sử dụng các số nguyên trung thực và giả trong ngữ cảnh Boolean, nhưng đó là một phản mẫu đã biết có thể khiến bạn mất nhiều giờ để gỡ lỗi không cần thiết. Tốt hơn hết là bạn nên theo Zen of Python để tránh rắc rối cho mình

Cuối cùng nhưng không kém phần quan trọng, bạn có thể cố ý muốn sử dụng các toán tử bitwise để vô hiệu hóa việc đánh giá ngắn mạch các biểu thức Boolean. Biểu thức sử dụng toán tử logic được đánh giá lười biếng từ trái sang phải. Nói cách khác, phép đánh giá dừng ngay khi biết kết quả của toàn bộ biểu thức

>>>

>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True

Trong ví dụ thứ hai, toán hạng bên phải hoàn toàn không được gọi vì giá trị của toàn bộ biểu thức đã được xác định bởi giá trị của toán hạng bên trái. Bất kể toán hạng bên phải là gì, nó sẽ không ảnh hưởng đến kết quả, vì vậy không có lý do gì để gọi nó trừ khi bạn dựa vào các tác dụng phụ

Có những thành ngữ, chẳng hạn như quay trở lại giá trị mặc định, tận dụng đặc thù này

>>>

>>> [42].bit_length[]
6
0

Một biểu thức Boolean lấy giá trị của toán hạng được đánh giá cuối cùng. Toán hạng trở thành true hoặc false bên trong biểu thức nhưng vẫn giữ nguyên kiểu và giá trị ban đầu của nó sau đó. Cụ thể, một số nguyên dương ở bên trái được lan truyền, trong khi số 0 bị loại bỏ

Không giống như các đối tác logic của chúng, các toán tử bitwise được đánh giá một cách háo hức

>>>

>>> [42].bit_length[]
6
1

Mặc dù biết toán hạng bên trái là đủ để xác định giá trị của toàn bộ biểu thức, tất cả các toán hạng luôn được đánh giá vô điều kiện

Trừ khi bạn có lý do chính đáng và biết mình đang làm gì, bạn chỉ nên sử dụng toán tử bitwise để kiểm soát bit. Nếu không thì quá dễ để hiểu sai. Trong hầu hết các trường hợp, bạn sẽ muốn chuyển số nguyên làm đối số cho toán tử bitwise

Bitwise AND

Toán tử AND theo bit [

>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
1] thực hiện kết hợp logic trên các bit tương ứng trong toán hạng của nó. Đối với mỗi cặp bit chiếm cùng một vị trí trong hai số, nó chỉ trả về một số khi cả hai bit được bật

Mẫu bit kết quả là giao điểm của các đối số của toán tử. Nó có hai bit được bật ở các vị trí mà cả hai toán hạng đều là một. Ở tất cả những nơi khác, ít nhất một trong các đầu vào có bit 0

Về mặt số học, điều này tương đương với tích của hai giá trị bit. Bạn có thể tính toán AND theo chiều bit của các số a và b bằng cách nhân các bit của chúng tại mọi chỉ số i

Đây là một ví dụ cụ thể

ExpressionBinary ValueDecimal Value

>>> [42].bit_length[]
6
3110011100215610
>>> [42].bit_length[]
6
3211010025210
>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
21010022010

Một nhân với một cho một, nhưng bất cứ điều gì nhân với 0 sẽ luôn dẫn đến kết quả bằng 0. Ngoài ra, bạn có thể lấy hai bit trong mỗi cặp. Lưu ý rằng khi các toán hạng có độ dài bit không bằng nhau, toán hạng ngắn hơn sẽ tự động được đệm bằng các số 0 ở bên trái

Bitwise HOẶC

Toán tử OR theo bit [

>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
3] thực hiện phân tách logic. Đối với mỗi cặp bit tương ứng, nó trả về một nếu ít nhất một trong số chúng được bật

Mẫu bit kết quả là sự kết hợp của các đối số của toán tử. Nó có năm bit được bật khi một trong hai toán hạng có một. Chỉ có sự kết hợp của hai số 0 mới cho số 0 ở đầu ra cuối cùng

Số học đằng sau nó là sự kết hợp của tổng và tích của các giá trị bit. Để tính toán OR theo bit của các số a và b, bạn cần áp dụng công thức sau cho các bit của chúng tại mọi chỉ số i

Đây là một ví dụ hữu hình

ExpressionBinary ValueDecimal Value

>>> [42].bit_length[]
6
3110011100215610
>>> [42].bit_length[]
6
3211010025210
>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
410111100218810

Nó gần giống như tổng của hai bit nhưng được kẹp ở đầu cao hơn để nó không bao giờ vượt quá giá trị của một. Bạn cũng có thể lấy hai bit trong mỗi cặp để có kết quả tương tự

Loại bỏ các quảng cáo

Bitwise XOR

Không giống như bitwise , và , toán tử XOR bitwise [

>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
5] không có đối chiếu logic trong Python. Tuy nhiên, bạn có thể mô phỏng nó bằng cách xây dựng trên các toán tử hiện có

>>> [42].bit_length[]
6
2

Nó đánh giá hai điều kiện loại trừ lẫn nhau và cho bạn biết liệu một trong số chúng có được đáp ứng hay không. Ví dụ: một người có thể là trẻ vị thành niên hoặc người lớn nhưng không thể đồng thời là cả hai. Ngược lại, một người không phải là trẻ vị thành niên cũng không phải là người lớn thì không thể. Sự lựa chọn là bắt buộc

Tên XOR là viết tắt của từ “độc quyền hoặc” vì nó thực hiện phân tách độc quyền trên các cặp bit. Nói cách khác, mỗi cặp bit phải chứa các giá trị bit đối lập để tạo ra một

Trực quan, đó là sự khác biệt đối xứng của các đối số của toán tử. Có ba bit được bật trong kết quả mà cả hai số có giá trị bit khác nhau. Các bit ở các vị trí còn lại bị triệt tiêu vì chúng giống nhau

Tương tự như toán tử OR theo bit, phép tính số học của XOR liên quan đến tổng. Tuy nhiên, trong khi bit OR kẹp các giá trị tại một, thì toán tử XOR bao quanh chúng bằng một tổng modulo hai

Modulo là hàm của hai số—số bị chia và số chia—thực hiện phép chia và trả về phần dư của nó. Trong Python, có một toán tử modulo tích hợp được biểu thị bằng dấu phần trăm [

>>> [42].bit_length[]
6
39]

Một lần nữa, bạn có thể xác nhận công thức bằng cách xem ví dụ

ExpressionBinary ValueDecimal Value

>>> [42].bit_length[]
6
3110011100215610
>>> [42].bit_length[]
6
3211010025210
>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
610101000216810

Tổng của hai số 0 hoặc hai số một chia cho hai sẽ được một số nguyên, vì vậy kết quả có số dư bằng 0. Tuy nhiên, khi bạn chia tổng của hai giá trị bit khác nhau cho hai, bạn sẽ nhận được một phân số có phần còn lại là một. Một công thức đơn giản hơn cho toán tử XOR là hiệu giữa giá trị lớn nhất và giá trị nhỏ nhất của cả hai bit trong mỗi cặp

Bitwise KHÔNG

Toán tử logic bitwise cuối cùng là toán tử NOT bitwise [

>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
7], chỉ mong đợi một đối số, làm cho nó trở thành toán tử bitwise đơn nguyên duy nhất. Nó thực hiện phép phủ định logic trên một số đã cho bằng cách lật tất cả các bit của nó

Các bit đảo ngược là phần bù của một, biến số 0 thành số 1 và số 1 thành số 0. Nó có thể được biểu diễn bằng số học dưới dạng phép trừ các giá trị bit riêng lẻ từ một

Đây là một ví dụ hiển thị một trong những số được sử dụng trước đây

ExpressionBinary ValueDecimal Value

>>> [42].bit_length[]
6
3110011100215610
>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
8110001129910

Mặc dù toán tử NOT bitwise dường như là đơn giản nhất trong số chúng, nhưng bạn cần hết sức thận trọng khi sử dụng nó trong Python. Mọi thứ bạn đã đọc cho đến nay đều dựa trên giả định rằng các số được biểu diễn bằng số nguyên không dấu

Ghi chú. Các kiểu dữ liệu không dấu không cho phép bạn lưu trữ các số âm như -273 vì không có khoảng trống cho dấu trong mẫu bit thông thường. Cố gắng làm như vậy sẽ dẫn đến lỗi biên dịch, ngoại lệ thời gian chạy hoặc tràn số nguyên tùy thuộc vào ngôn ngữ được sử dụng

Mặc dù có nhiều cách để mô phỏng, nhưng Python không hỗ trợ chúng một cách tự nhiên. Điều đó có nghĩa là tất cả các số đều có một dấu hiệu ngầm gắn liền với chúng cho dù bạn có chỉ định hay không. Điều này cho thấy khi bạn thực hiện KHÔNG theo chiều bit của bất kỳ số nào

>>>

>>> [42].bit_length[]
6
3

Thay vì 9910 như mong đợi, bạn nhận được giá trị âm. Lý do cho điều này sẽ trở nên rõ ràng khi bạn tìm hiểu về các. Hiện tại, giải pháp khắc phục nhanh là tận dụng toán tử AND theo bit

>>>

>>> [42].bit_length[]
6
4

Đó là một ví dụ hoàn hảo về , mà bạn sẽ khám phá ở một trong những phần sắp tới

Loại bỏ các quảng cáo

Toán tử dịch chuyển theo bit

Toán tử dịch chuyển bit là một loại công cụ khác để thao tác bit. Chúng cho phép bạn di chuyển các bit xung quanh, điều này sẽ hữu ích cho việc tạo các mặt nạ bit sau này. Trước đây, chúng thường được sử dụng để cải thiện tốc độ của một số phép toán nhất định

Dịch trái

Toán tử dịch trái theo chiều bit [

>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
9] di chuyển các bit của toán hạng thứ nhất sang trái theo số vị trí được chỉ định trong toán hạng thứ hai của nó. Nó cũng quan tâm đến việc chèn đủ bit 0 để lấp đầy khoảng trống phát sinh ở cạnh phải của mẫu bit mới

Dịch chuyển một bit sang trái một vị trí sẽ nhân đôi giá trị của nó. Ví dụ: thay vì số hai, bit sẽ chỉ số bốn sau khi dịch chuyển. Di chuyển nó sang bên trái hai vị trí sẽ tăng gấp bốn lần giá trị kết quả. Khi bạn cộng tất cả các bit thành một số nhất định, bạn sẽ nhận thấy rằng nó cũng được nhân đôi với mỗi vị trí được thay đổi

ExpressionBinary ValueDecimal Value

>>> [42].bit_length[]
6
3110011123910
>>> [42].bit_length[]
6
48100111027810
>>> [42].bit_length[]
6
4910011100215610
>>> [42].bit_length[]
6
50100111000231210

Nói chung, dịch chuyển các bit sang trái tương ứng với việc nhân một số với lũy thừa hai, với số mũ bằng với số vị trí được dịch chuyển

Dịch trái từng là một kỹ thuật tối ưu hóa phổ biến vì dịch chuyển bit là một lệnh đơn lẻ và tính toán rẻ hơn so với số mũ hoặc tích. Tuy nhiên, ngày nay, các trình biên dịch và trình thông dịch, bao gồm cả Python, hoàn toàn có khả năng tối ưu hóa mã của bạn đằng sau hậu trường

Ghi chú. Không sử dụng toán tử dịch chuyển bit làm phương tiện tối ưu hóa sớm trong Python. Bạn sẽ không thấy sự khác biệt về tốc độ thực thi, nhưng chắc chắn bạn sẽ làm cho mã của mình khó đọc hơn

Trên giấy, mẫu bit do dịch chuyển trái sẽ dài hơn ở nhiều vị trí khi bạn dịch chuyển nó. Điều đó cũng đúng với Python nói chung vì cách nó xử lý các số nguyên. Tuy nhiên, trong hầu hết các trường hợp thực tế, bạn sẽ muốn hạn chế độ dài của mẫu bit là bội số của tám, là độ dài byte tiêu chuẩn

Ví dụ: nếu bạn đang làm việc với một byte đơn, thì việc dịch chuyển nó sang bên trái sẽ loại bỏ tất cả các bit vượt ra ngoài ranh giới bên trái của nó

Nó giống như nhìn vào một luồng bit không giới hạn thông qua một cửa sổ có độ dài cố định. Có một vài thủ thuật cho phép bạn làm điều này trong Python. Ví dụ: bạn có thể áp dụng a với toán tử AND theo bit

>>>

>>> [42].bit_length[]
6
5

Dịch chuyển 3910 ba vị trí sang trái sẽ trả về một số cao hơn giá trị tối đa mà bạn có thể lưu trữ trên một byte đơn. Phải mất chín bit, trong khi một byte chỉ có tám. Để cắt bớt một bit thừa ở bên trái, bạn có thể áp dụng một bitmask với giá trị phù hợp. Nếu bạn muốn giữ nhiều hoặc ít bit hơn, thì bạn sẽ cần sửa đổi giá trị mặt nạ cho phù hợp

Ca phải

Toán tử dịch phải theo chiều bit [

>>> [42].bit_length[]
6
01] tương tự như toán tử bên trái, nhưng thay vì di chuyển các bit sang trái, nó đẩy chúng sang phải theo số vị trí đã chỉ định. Các bit ngoài cùng bên phải luôn bị loại bỏ

Mỗi khi bạn dịch chuyển một chút sang phải một vị trí, bạn sẽ giảm một nửa giá trị cơ bản của nó. Di chuyển cùng một bit sang bên phải hai vị trí sẽ tạo ra một phần tư giá trị ban đầu, v.v. Khi bạn cộng tất cả các bit riêng lẻ, bạn sẽ thấy quy tắc tương tự áp dụng cho số mà chúng đại diện

ExpressionBinary ValueDecimal Value

>>> [42].bit_length[]
6
3110011101215710
>>> [42].bit_length[]
6
53100111027810
>>> [42].bit_length[]
6
5410011123910
>>> [42].bit_length[]
6
551001121910

Giảm một nửa số lẻ chẳng hạn như 15710 sẽ tạo ra một phân số. Để thoát khỏi nó, toán tử shift phải sẽ tự động kết quả. Nó hầu như giống như phép chia tầng bằng lũy ​​thừa hai

Một lần nữa, số mũ tương ứng với số lượng vị trí dịch chuyển sang phải. Trong Python, bạn có thể tận dụng một toán tử chuyên dụng để thực hiện phân chia tầng

>>>

>>> [42].bit_length[]
6
6

Cả toán tử dịch chuyển phải theo chiều bit và toán tử chia tầng đều hoạt động theo cùng một cách, ngay cả đối với các số âm. Tuy nhiên, phép chia sàn cho phép bạn chọn bất kỳ ước số nào và không chỉ là lũy thừa của hai. Sử dụng dịch chuyển phải theo chiều bit là một cách phổ biến để cải thiện hiệu suất của một số phép chia số học

Ghi chú. Bạn có thể tự hỏi điều gì sẽ xảy ra khi bạn hết bit để chuyển. Ví dụ: khi bạn thử đẩy nhiều vị trí hơn số bit trong một số

>>>

>>> [42].bit_length[]
6
7

Khi không còn bit nào được bật, bạn sẽ bị mắc kẹt với giá trị bằng 0. Số không chia cho bất cứ thứ gì sẽ luôn trả về số không. Tuy nhiên, mọi thứ trở nên phức tạp hơn khi bạn chuyển sang phải một số âm vì bit dấu ẩn cản trở

>>>

>>> [42].bit_length[]
6
8

Quy tắc ngón tay cái là, bất kể dấu hiệu là gì, kết quả sẽ giống như phép chia sàn cho một số lũy thừa của hai. Sàn của một phân số âm nhỏ luôn là âm một, và đó là những gì bạn sẽ nhận được. Đọc tiếp để được giải thích chi tiết hơn

Cũng giống như toán tử dịch trái, mẫu bit thay đổi kích thước của nó sau khi dịch phải. Mặc dù di chuyển các bit sang phải làm cho chuỗi nhị phân ngắn hơn, nhưng điều đó thường không thành vấn đề vì bạn có thể đặt bao nhiêu số 0 trước chuỗi bit tùy thích mà không thay đổi giá trị. Ví dụ: 1012 giống như 01012 và 000001012 cũng vậy, miễn là bạn đang xử lý các số không âm

Đôi khi, bạn sẽ muốn giữ một độ dài bit nhất định sau khi thực hiện dịch chuyển sang phải để căn chỉnh nó với một giá trị khác hoặc để khớp với một nơi nào đó. Bạn có thể làm điều đó bằng cách áp dụng một bitmask

Nó chỉ cắt ra những bit mà bạn quan tâm và điền vào mẫu bit với các số 0 đứng đầu nếu cần

Việc xử lý các số âm trong Python hơi khác so với cách tiếp cận truyền thống để dịch chuyển bit. Trong phần tiếp theo, bạn sẽ kiểm tra điều này chi tiết hơn

Loại bỏ các quảng cáo

Số học vs Dịch chuyển logic

Bạn có thể phân loại thêm các toán tử dịch chuyển bit thành toán tử dịch chuyển số học và logic. Mặc dù Python chỉ cho phép bạn thực hiện phép dịch chuyển số học, nhưng bạn nên biết cách các ngôn ngữ lập trình khác triển khai các toán tử dịch chuyển bit để tránh nhầm lẫn và bất ngờ

Sự khác biệt này xuất phát từ cách chúng xử lý bit dấu, thường nằm ở cạnh ngoài cùng bên trái của chuỗi nhị phân có dấu. Trong thực tế, nó chỉ liên quan đến toán tử dịch chuyển phải, điều này có thể khiến một số bị đảo dấu, dẫn đến tràn số nguyên

Ghi chú. Ví dụ, Java và JavaScript phân biệt toán tử dịch phải logic bằng một dấu lớn hơn bổ sung [

>>> [42].bit_length[]
6
03]. Vì toán tử dịch chuyển trái hoạt động nhất quán trên cả hai loại ca, nên các ngôn ngữ này không xác định đối chiếu dịch trái hợp lý

Thông thường, một bit dấu bật cho biết các số âm, giúp giữ các thuộc tính số học của một chuỗi nhị phân

Giá trị thập phân Giá trị nhị phân đã kýSign BitSignMeaning-100101001110021-Số âm28100001110020+Số dương hoặc số không

Nhìn từ bên trái vào hai dãy nhị phân này, bạn có thể thấy rằng bit đầu tiên của chúng mang thông tin về dấu hiệu, trong khi phần còn lại bao gồm các bit độ lớn, giống nhau cho cả hai số

Ghi chú. Các giá trị thập phân cụ thể sẽ phụ thuộc vào cách bạn quyết định biểu thị các số có dấu ở dạng nhị phân. Nó khác nhau giữa các ngôn ngữ và thậm chí còn phức tạp hơn trong Python, vì vậy bạn có thể tạm thời bỏ qua nó. Bạn sẽ có một bức tranh đẹp hơn khi bạn đến phần bên dưới

Dịch chuyển phải hợp lý, còn được gọi là dịch chuyển phải không dấu hoặc dịch chuyển phải không điền, di chuyển toàn bộ chuỗi nhị phân, bao gồm cả bit dấu và lấp đầy khoảng trống kết quả ở bên trái bằng số 0

Chú ý thông tin về dấu của số bị mất. Bất kể dấu ban đầu là gì, nó sẽ luôn tạo ra một số nguyên không âm vì bit dấu được thay thế bằng 0. Miễn là bạn không quan tâm đến các giá trị số, thì một phép dịch phải hợp lý có thể hữu ích trong việc xử lý dữ liệu nhị phân cấp thấp

Tuy nhiên, vì các số nhị phân có dấu thường được lưu trữ trên một chuỗi bit có độ dài cố định trong hầu hết các ngôn ngữ nên nó có thể làm cho kết quả bao quanh các giá trị cực đoan. Bạn có thể thấy điều này trong công cụ Java Shell tương tác

>>> [42].bit_length[]
6
9

Số kết quả thay đổi dấu của nó từ âm sang dương, nhưng nó cũng bị tràn, kết thúc rất gần với số nguyên tối đa của Java

>>> len["€uro".encode["utf-8"]]
6
0

Con số này thoạt nhìn có vẻ tùy ý, nhưng nó liên quan trực tiếp đến số lượng bit mà Java phân bổ cho kiểu dữ liệu

>>> [42].bit_length[]
6
57

>>> len["€uro".encode["utf-8"]]
6
1

Nó sử dụng 32 bit để lưu trữ trong biểu diễn bù hai. Khi bạn lấy bit dấu ra, bạn còn lại 31 bit, có giá trị thập phân tối đa bằng 231 - 1 hoặc 214748364710

Mặt khác, Python lưu trữ các số nguyên như thể có vô số bit theo ý của bạn. Do đó, một toán tử dịch phải hợp lý sẽ không được xác định rõ trong Python thuần túy, do đó, nó bị thiếu trong ngôn ngữ. Tuy nhiên, bạn vẫn có thể mô phỏng nó

Một cách để làm như vậy là tận dụng các kiểu dữ liệu không dấu có sẵn trong C được hiển thị thông qua mô-đun

>>> [42].bit_length[]
6
58 tích hợp sẵn

>>>

>>> len["€uro".encode["utf-8"]]
6
2

Họ cho phép bạn nhập một số âm nhưng không gắn bất kỳ ý nghĩa đặc biệt nào với bit dấu. Nó được xử lý giống như phần còn lại của các bit cường độ

Mặc dù chỉ có một số loại số nguyên không dấu được xác định trước trong C, khác nhau về độ dài bit, nhưng bạn có thể tạo một hàm tùy chỉnh trong Python để xử lý các độ dài bit tùy ý

>>>

>>> len["€uro".encode["utf-8"]]
6
3

Thao tác này chuyển đổi chuỗi bit có dấu thành chuỗi không dấu và sau đó thực hiện phép dịch phải số học thông thường

Tuy nhiên, vì các chuỗi bit trong Python không cố định về độ dài nên chúng không thực sự có bit dấu. Hơn nữa, chúng không sử dụng biểu diễn bù hai truyền thống như trong C hoặc Java. Để giảm thiểu điều đó, bạn có thể tận dụng thao tác modulo, thao tác này sẽ giữ nguyên các mẫu bit ban đầu cho các số nguyên dương trong khi bao quanh các số nguyên âm một cách thích hợp

Phép dịch phải số học [

>>> [42].bit_length[]
6
01], đôi khi được gọi là toán tử dịch phải có dấu, duy trì dấu của một số bằng cách sao chép bit dấu của nó trước khi di chuyển các bit sang phải

Nói cách khác, nó lấp đầy khoảng trống ở bên trái bằng bất kỳ bit dấu nào. Kết hợp với biểu diễn bù hai của nhị phân có dấu, điều này dẫn đến một giá trị chính xác về mặt số học. Bất kể số đó là dương hay âm, một phép dịch phải số học tương đương với phép chia sàn

Như bạn sắp tìm hiểu, Python không phải lúc nào cũng lưu trữ các số nguyên ở dạng nhị phân bù hai đơn giản. Thay vào đó, nó tuân theo một chiến lược thích ứng tùy chỉnh hoạt động giống như với số lượng bit không giới hạn. Nó chuyển đổi các số qua lại giữa biểu diễn bên trong của chúng và phần bù hai để bắt chước hành vi tiêu chuẩn của phép dịch số học

Loại bỏ các quảng cáo

Đại diện số nhị phân

Bạn đã trực tiếp trải nghiệm việc thiếu các kiểu dữ liệu không dấu trong Python khi sử dụng phủ định bitwise [

>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
7] và toán tử dịch chuyển phải [
>>> [42].bit_length[]
6
01]. Bạn đã thấy các gợi ý về cách tiếp cận bất thường để lưu trữ , khiến việc xử lý các số âm trở nên khó khăn. Để sử dụng các toán tử bitwise một cách hiệu quả, bạn cần biết về các cách biểu diễn khác nhau của các số trong hệ nhị phân

Số nguyên không dấu

Trong các ngôn ngữ lập trình như C, bạn chọn sử dụng kiểu có dấu hoặc không dấu của một loại số nhất định. Các kiểu dữ liệu không dấu sẽ phù hợp hơn khi bạn biết chắc rằng mình sẽ không bao giờ phải xử lý các số âm. Bằng cách phân bổ một bit bổ sung đó, nếu không sẽ đóng vai trò là bit dấu, bạn thực tế đã nhân đôi phạm vi giá trị khả dụng

Nó cũng làm cho mọi thứ an toàn hơn một chút bằng cách tăng giới hạn tối đa trước khi tràn xảy ra. Tuy nhiên, tràn chỉ xảy ra với độ dài bit cố định, vì vậy chúng không liên quan đến Python, vốn không có các ràng buộc như vậy

Cách nhanh nhất để tìm hiểu về các kiểu số không dấu trong Python là sử dụng mô-đun

>>> [42].bit_length[]
6
58 đã đề cập trước đó

>>>

>>> len["€uro".encode["utf-8"]]
6
4

Vì không có bit dấu trong các số nguyên như vậy nên tất cả các bit của chúng biểu thị độ lớn của một số. Việc truyền một số âm buộc Python phải diễn giải lại mẫu bit như thể nó chỉ có các bit cường độ

Số nguyên có dấu

Dấu của một số chỉ có hai trạng thái. Nếu bạn bỏ qua số 0 trong giây lát, thì nó có thể dương hoặc âm, điều này chuyển thành hệ thống nhị phân một cách độc đáo. Tuy nhiên, có một số cách khác để biểu diễn các số nguyên đã ký ở dạng nhị phân, mỗi cách đều có ưu và nhược điểm riêng.

Có lẽ cách đơn giản nhất là độ lớn của dấu, được xây dựng một cách tự nhiên trên các số nguyên không dấu. Khi một chuỗi nhị phân được hiểu là độ lớn dấu, nó đóng vai trò của một bit dấu, trong khi các bit còn lại hoạt động giống như bình thường

Chuỗi nhị phân Giá trị độ lớn ký Giá trị không dấu00101010242104210101010102-421017010

Số 0 ở bit ngoài cùng bên trái biểu thị số dương [

>>> [42].bit_length[]
6
63] và số 1 biểu thị số âm [
>>> [42].bit_length[]
6
64]. Lưu ý rằng một bit dấu không đóng góp vào giá trị tuyệt đối của số trong biểu diễn cường độ dấu. Nó chỉ ở đó để cho phép bạn lật dấu của các bit còn lại

Tại sao bit ngoài cùng bên trái?

Nó giữ nguyên chỉ mục bit, do đó, giúp duy trì khả năng tương thích ngược của trọng số bit được sử dụng để tính giá trị thập phân của chuỗi nhị phân. Tuy nhiên, không phải mọi thứ về độ lớn của dấu hiệu đều tuyệt vời như vậy

Ghi chú. Các biểu diễn nhị phân của các số nguyên đã ký chỉ có ý nghĩa đối với các chuỗi bit có độ dài cố định. Mặt khác, bạn không thể biết vị trí của bit dấu. Tuy nhiên, trong Python, bạn có thể biểu diễn số nguyên bằng bao nhiêu bit tùy thích

>>>

>>> len["€uro".encode["utf-8"]]
6
5

Cho dù đó là bốn bit hay tám bit, bit dấu sẽ luôn được tìm thấy ở vị trí ngoài cùng bên trái

Phạm vi giá trị mà bạn có thể lưu trữ trong mẫu bit biên độ ký hiệu là đối xứng. Nhưng điều đó cũng có nghĩa là bạn sẽ có hai cách để truyền đạt số không

Chuỗi nhị phân Giá trị độ lớn ký Giá trị không dấu000000002+010010100000002-01012810

Về mặt kỹ thuật, số 0 không có ký hiệu, nhưng không có cách nào để không bao gồm một ký hiệu trong cường độ ký hiệu. Mặc dù có một số 0 mơ hồ không phải là lý tưởng trong hầu hết các trường hợp, nhưng đó không phải là phần tồi tệ nhất của câu chuyện. Nhược điểm lớn nhất của phương pháp này là số học nhị phân cồng kềnh

Khi bạn áp dụng số học nhị phân tiêu chuẩn cho các số được lưu trữ ở ký hiệu, nó có thể không mang lại cho bạn kết quả như mong đợi. Ví dụ: cộng hai số có cùng độ lớn nhưng ngược dấu sẽ không làm cho chúng bị triệt tiêu

ExpressionBinary SequenceSign-Magnitude Value

>>> [42].bit_length[]
6
310010101024210
>>> [42].bit_length[]
6
32101010102-4210
>>> [42].bit_length[]
6
67110101002-8410

Tổng của 42 và -42 không tạo ra số 0. Ngoài ra, bit chuyển đổi đôi khi có thể truyền từ độ lớn sang bit dấu, đảo ngược dấu hiệu và mang lại kết quả không mong muốn

Để giải quyết những vấn đề này, một số máy tính đời đầu đã sử dụng biểu diễn bổ sung của một người. Ý tưởng là thay đổi cách các số thập phân được ánh xạ tới các chuỗi nhị phân cụ thể để chúng có thể được cộng lại một cách chính xác. Để tìm hiểu sâu hơn về phần bổ sung của một người, bạn có thể mở rộng phần bên dưới

Phần bù của một ngườiHiển thị/Ẩn

Trong phần bù của một số, các số dương giống như trong độ lớn của dấu hiệu, nhưng các số âm có được bằng cách lật các bit của số dương bằng cách sử dụng một bit NOT

Dãy dương Dãy âm Giá trị độ lớn000000002111111112±010000000012111111102±110000000102111111012±210⋮⋮⋮011111112100000002±12710

Điều này giữ nguyên ý nghĩa ban đầu của bit dấu, vì vậy các số dương vẫn bắt đầu bằng số 0 nhị phân, trong khi số âm bắt đầu bằng số nhị phân. Tương tự như vậy, phạm vi giá trị vẫn đối xứng và tiếp tục có hai cách biểu diễn số 0. Tuy nhiên, chuỗi nhị phân của các số âm trong phần bù của một người được sắp xếp theo thứ tự ngược lại so với độ lớn của dấu

Một số bổ sung-magnitudedecimal value111111112100000002-010111111102100000012-110111111012100000102-210

Nhờ đó, giờ đây bạn có thể cộng hai số một cách đáng tin cậy hơn vì bit dấu không cần xử lý đặc biệt. Nếu một chuyển đổi bắt nguồn từ bit dấu, nó sẽ được đưa trở lại ở cạnh phải của chuỗi nhị phân thay vì chỉ bị loại bỏ. Điều này đảm bảo kết quả chính xác

Tuy nhiên, các máy tính hiện đại không sử dụng phần bù một để biểu diễn các số nguyên vì có một cách thậm chí còn tốt hơn được gọi là phần bù hai. Bằng cách áp dụng một sửa đổi nhỏ, bạn có thể loại bỏ hai số 0 và đơn giản hóa số học nhị phân trong một lần. Để khám phá phần bù của hai chi tiết hơn, bạn có thể mở rộng phần bên dưới

Phần bù của haiHiển thị/Ẩn

Khi tìm các chuỗi bit có giá trị âm trong phần bù hai, mẹo là thêm một vào kết quả sau khi phủ định các bit

Dãy dươngPhần bù của một [NOT]Phần bù của hai [NOT+1]000000002111111112000000002000000012111111102111111112000000102111110121111111102⋮⋮⋮0111110001000

Điều này đẩy chuỗi bit của các số âm xuống một vị trí, loại bỏ dấu trừ 0 khét tiếng. Thay vào đó, một dấu trừ hữu ích hơn sẽ chiếm lấy mẫu bit của nó

Là một tác dụng phụ, phạm vi các giá trị có sẵn trong phần bù của hai trở nên không đối xứng, với giới hạn dưới là lũy thừa của hai và giới hạn trên là số lẻ. Ví dụ: số nguyên có dấu 8 bit sẽ cho phép bạn lưu trữ các số từ -12810 đến 12710 trong phần bù hai

Hai bổ sung bổ sung Giá trị bổ sung100000002N/A-12810100000012100000002-12710100000102100000012-12610

Một cách khác để nói rằng bit quan trọng nhất mang cả dấu và một phần của độ lớn số

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3Bit 2Bit 1Bit 0-2726252423222120-1286432168421

Lưu ý dấu trừ bên cạnh trọng lượng bit ngoài cùng bên trái. Lấy một giá trị thập phân từ một chuỗi nhị phân như thế chỉ là vấn đề thêm các cột thích hợp. Ví dụ: giá trị của 110101102 trong biểu diễn bù hai 8 bit giống với tổng. -12810 + 6410 + 1610 + 410 + 210 = -4210

Với biểu diễn bổ sung của cả hai, bạn không còn phải lo lắng về bit chuyển tiếp trừ khi bạn muốn sử dụng nó như một cơ chế phát hiện tràn, điều này khá gọn gàng.

Có một vài biến thể khác của biểu diễn số có dấu, nhưng chúng không phổ biến bằng

Loại bỏ các quảng cáo

Số dấu phẩy động

Tiêu chuẩn IEEE 754 xác định biểu diễn nhị phân cho các số thực bao gồm các bit dấu, số mũ và phần định trị. Không đi sâu vào quá nhiều chi tiết kỹ thuật, bạn có thể coi nó như một ký hiệu khoa học cho các số nhị phân. Dấu thập phân "trôi" xung quanh để chứa một số lượng khác nhau của các số liệu quan trọng, ngoại trừ đó là một điểm nhị phân

Hai loại dữ liệu phù hợp với tiêu chuẩn đó được hỗ trợ rộng rãi

  1. Độ chính xác đơn. 1 bit dấu, 8 bit số mũ, 23 bit định trị
  2. Độ chính xác gấp đôi. 1 bit dấu, 11 bit số mũ, 52 bit định trị

Kiểu dữ liệu

>>> [42].bit_length[]
6
68 của Python tương đương với kiểu chính xác kép. Lưu ý rằng một số ứng dụng yêu cầu nhiều hoặc ít bit hơn. Ví dụ: định dạng hình ảnh OpenEXR tận dụng độ chính xác một nửa để biểu thị các pixel có dải màu động cao ở kích thước tệp hợp lý

Số Pi [π] có biểu diễn nhị phân sau với độ chính xác duy nhất khi được làm tròn đến năm chữ số thập phân

Ký số mũMantissa02100000002. 100100100001111110100002

Bit dấu hoạt động giống như với số nguyên, vì vậy số 0 biểu thị số dương. Tuy nhiên, đối với số mũ và phần định trị, các quy tắc khác nhau có thể áp dụng tùy thuộc vào một số trường hợp cạnh

Trước tiên, bạn cần chuyển đổi chúng từ dạng nhị phân sang dạng thập phân

  • số mũ. 12810
  • bọ ngựa. 2-1 + 2-4 + … + 2-19 = 29926110/52428810 ≈ 0. 57079510

Số mũ được lưu dưới dạng số nguyên không dấu, nhưng để tính các giá trị âm, nó thường có độ lệch bằng 12710 với độ chính xác đơn. Bạn cần trừ nó để khôi phục số mũ thực tế

Các bit định trị đại diện cho một phân số, vì vậy chúng tương ứng với các lũy thừa âm của hai. Ngoài ra, bạn cần thêm một vào phần định trị vì nó giả định một bit dẫn trước ẩn trước điểm cơ số trong trường hợp cụ thể này

Đặt tất cả lại với nhau, bạn đi đến công thức sau để chuyển đổi số nhị phân dấu phẩy động thành số thập phân

Khi bạn thay thế các biến cho các giá trị thực tế trong ví dụ trên, bạn sẽ có thể giải mã mẫu bit của một số dấu phẩy động được lưu trữ với độ chính xác đơn

Đây rồi, miễn là Pi đã được làm tròn đến năm chữ số thập phân. Bạn sẽ học cách làm sau này

Số điểm cố định

Mặc dù các số dấu phẩy động rất phù hợp cho các mục đích kỹ thuật, nhưng chúng không thành công trong tính toán tiền tệ do độ chính xác hạn chế của chúng. Ví dụ: một số số có biểu diễn hữu hạn trong ký hiệu thập phân chỉ có biểu diễn vô hạn trong hệ nhị phân. Điều đó thường dẫn đến lỗi làm tròn, có thể tích lũy theo thời gian

>>>

>>> len["€uro".encode["utf-8"]]
6
6

Trong những trường hợp như vậy, tốt hơn hết bạn nên sử dụng mô-đun của Python, mô-đun này triển khai số học điểm cố định và cho phép bạn chỉ định vị trí đặt dấu thập phân trên một độ dài bit nhất định. Ví dụ: bạn có thể cho biết bạn muốn giữ nguyên bao nhiêu chữ số

>>>

>>> len["€uro".encode["utf-8"]]
6
7

Tuy nhiên, nó bao gồm tất cả các chữ số, không chỉ các phân số

Ghi chú. Nếu bạn đang làm việc với các số hữu tỉ, thì bạn có thể quan tâm đến việc kiểm tra mô-đun

>>> [42].bit_length[]
6
70, đây là một phần của thư viện chuẩn của Python

Nếu bạn không thể hoặc không muốn sử dụng loại dữ liệu điểm cố định, thì một cách đơn giản để lưu trữ giá trị tiền tệ một cách đáng tin cậy là chia tỷ lệ số tiền thành đơn vị nhỏ nhất, chẳng hạn như xu và biểu thị chúng bằng số nguyên

Loại bỏ các quảng cáo

Số nguyên trong Python

Ngày xưa của lập trình, bộ nhớ máy tính rất cao. Do đó, các ngôn ngữ sẽ cung cấp cho bạn quyền kiểm soát khá chi tiết về số lượng byte cần phân bổ cho dữ liệu của bạn. Hãy xem nhanh một vài kiểu số nguyên từ C làm ví dụ

TypeSizeMinimum ValueMaximum Value

>>> [42].bit_length[]
6
711 byte-128127
>>> [42].bit_length[]
6
722 bytes-32,76832,767
>>> [42].bit_length[]
6
734 bytes-2,147,483,6482,147,483,647
>>> [42].bit_length[]
6
748 bytes-9,223,372,036,854,775,8089,223,372,036,854,775,807

Các giá trị này có thể thay đổi từ nền tảng này sang nền tảng khác. Tuy nhiên, sự phong phú của các loại số như vậy cho phép bạn sắp xếp dữ liệu trong bộ nhớ một cách gọn gàng. Hãy nhớ rằng những thứ này thậm chí không bao gồm các loại không dấu

Ở phía bên kia của quang phổ là các ngôn ngữ như JavaScript, chỉ có một loại số để cai trị tất cả chúng. Mặc dù điều này ít gây nhầm lẫn hơn đối với những người mới bắt đầu lập trình, nhưng nó phải trả giá bằng việc tăng mức tiêu thụ bộ nhớ, giảm hiệu quả xử lý và giảm độ chính xác

Khi nói về các toán tử bitwise, điều cần thiết là phải hiểu cách Python xử lý các số nguyên. Rốt cuộc, bạn sẽ chủ yếu sử dụng các toán tử này để làm việc với các số nguyên. Có một vài cách biểu diễn số nguyên cực kỳ khác nhau trong Python phụ thuộc vào giá trị của chúng

số nguyên nội

Trong CPython, các số nguyên rất nhỏ trong khoảng từ -510 đến 25610 nằm trong bộ đệm chung để đạt được một số hiệu suất vì các số trong phạm vi đó thường được sử dụng. Trong thực tế, bất cứ khi nào bạn đề cập đến một trong những giá trị đó, là những giá trị đơn lẻ được tạo khi khởi động trình thông dịch, Python sẽ luôn cung cấp cùng một phiên bản

>>>

>>> len["€uro".encode["utf-8"]]
6
8

Cả hai biến đều giống nhau vì chúng đề cập đến cùng một đối tượng trong bộ nhớ. Đó là điển hình của các loại tham chiếu nhưng không phải là giá trị bất biến, chẳng hạn như số nguyên. Tuy nhiên, khi bạn vượt ra ngoài phạm vi giá trị được lưu trong bộ nhớ cache đó, Python sẽ bắt đầu tạo các bản sao riêng biệt trong quá trình gán biến

>>>

>>> len["€uro".encode["utf-8"]]
6
9

Mặc dù có các giá trị bằng nhau, các biến này hiện đang trỏ đến các đối tượng riêng biệt. Nhưng đừng để điều đó đánh lừa bạn. Python sẽ thỉnh thoảng nhảy vào và tối ưu hóa mã của bạn ở hậu trường. Ví dụ: nó sẽ lưu vào bộ đệm một số xuất hiện trên cùng một dòng nhiều lần bất kể giá trị của nó là gì

>>> ______39_______0

Các biến

>>> [42].bit_length[]
6
31 và
>>> [42].bit_length[]
6
32 là các đối tượng độc lập vì chúng nằm ở các vị trí bộ nhớ khác nhau, trong khi các số được sử dụng theo nghĩa đen trong ____11_______77 thực tế là cùng một đối tượng

Ghi chú. Thực tập là một chi tiết triển khai của trình thông dịch CPython, có thể thay đổi trong các phiên bản sau, vì vậy đừng dựa vào nó trong các chương trình của bạn

Thật thú vị, có một cơ chế thực tập chuỗi tương tự trong Python, khởi động cho các văn bản ngắn chỉ bao gồm các chữ cái ASCII. Nó giúp tăng tốc độ tra cứu từ điển bằng cách cho phép so sánh các khóa của chúng theo địa chỉ bộ nhớ hoặc con trỏ C, thay vì theo từng ký tự chuỗi riêng lẻ

Số nguyên có độ chính xác cố định

Các số nguyên mà bạn có nhiều khả năng tìm thấy nhất trong Python sẽ tận dụng kiểu dữ liệu C

>>> [42].bit_length[]
6
78. Họ sử dụng biểu diễn nhị phân bổ sung của hai cổ điển trên một số bit cố định. Độ dài bit chính xác sẽ phụ thuộc vào nền tảng phần cứng, hệ điều hành và phiên bản trình thông dịch Python của bạn

Các máy tính hiện đại thường sử dụng kiến ​​trúc 64-bit, vì vậy điều này sẽ chuyển thành số thập phân giữa -263 và 263 - 1. Bạn có thể kiểm tra giá trị lớn nhất của số nguyên có độ chính xác cố định trong Python theo cách sau

>>>

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

Nó rất lớn. Khoảng 9 triệu lần số lượng các ngôi sao trong thiên hà của chúng ta, vì vậy nó đủ để sử dụng hàng ngày. Mặc dù giá trị tối đa mà bạn có thể vắt ra từ loại

>>> [42].bit_length[]
6
79 trong C thậm chí còn lớn hơn, theo thứ tự 1019, các số nguyên trong Python không có giới hạn về mặt lý thuyết. Để cho phép điều này, các số không phù hợp với chuỗi bit có độ dài cố định được lưu trữ khác nhau trong bộ nhớ

Loại bỏ các quảng cáo

Số nguyên chính xác tùy ý

Bạn có nhớ bài hát K-pop nổi tiếng “Gangnam Style” đã trở thành hit trên toàn thế giới vào năm 2012 không? . Ngay sau đó, rất nhiều người đã xem video khiến bộ đếm lượt xem tràn ngập. YouTube không có lựa chọn nào khác ngoài việc nâng cấp bộ đếm của họ từ số nguyên có chữ ký 32 bit lên số nguyên 64 bit

Điều đó có thể mang lại nhiều khoảng trống cho quầy xem, nhưng thậm chí còn có những con số lớn hơn không phải là hiếm trong cuộc sống thực, đặc biệt là trong thế giới khoa học. Tuy nhiên, Python có thể xử lý chúng một cách dễ dàng

>>>

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

Số này có năm mươi hai chữ số thập phân. Sẽ mất ít nhất 170 bit để biểu diễn nó ở dạng nhị phân với cách tiếp cận truyền thống

>>>

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

Vì chúng vượt quá giới hạn mà bất kỳ loại C nào cũng phải cung cấp, nên các số thiên văn như vậy được chuyển đổi thành một hệ thống vị trí có độ lớn ký hiệu, có cơ sở là 230. Vâng, bạn đã đọc dúng điều đó. Trong khi bạn có mười ngón tay, Python có hơn một tỷ

Một lần nữa, điều này có thể khác nhau tùy thuộc vào nền tảng bạn hiện đang sử dụng. Khi nghi ngờ, bạn có thể kiểm tra lại

>>>

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

Điều này sẽ cho bạn biết có bao nhiêu bit được sử dụng trên mỗi chữ số và kích thước tính bằng byte của cấu trúc C bên dưới. Để có được điều tương tự trong Python 2, thay vào đó, bạn nên tham khảo thuộc tính

>>> [42].bit_length[]
6
80

Mặc dù việc chuyển đổi này giữa các số nguyên có độ chính xác cố định và độ chính xác tùy ý được thực hiện liền mạch trong Python 3, nhưng đã có lúc mọi thứ trở nên rõ ràng hơn. Để biết thêm thông tin, bạn có thể mở rộng hộp bên dưới

>>> [42].bit_length[]
6
73 và
>>> [42].bit_length[]
6
74 trong Python 2Hiển thị/Ẩn

Trước đây, Python đã định nghĩa rõ ràng hai loại số nguyên riêng biệt

  1. số nguyên đơn giản
  2. Số nguyên dài

Loại đầu tiên được mô phỏng theo loại C

>>> [42].bit_length[]
6
78, thường chiếm 32 hoặc 64 bit và cung cấp một phạm vi giá trị hạn chế

>>>

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

Đối với số lượng lớn hơn, bạn phải sử dụng loại thứ hai không có giới hạn. Python sẽ tự động thăng cấp số nguyên đơn giản thành số nguyên dài nếu cần

>>>

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

Tính năng này đã ngăn chặn lỗi tràn số nguyên. Lưu ý chữ cái

>>> [42].bit_length[]
6
84 ở cuối một chữ cái, có thể được sử dụng để thực thi loại đã cho bằng tay

>>>

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

Cuối cùng, cả hai loại đã được thống nhất để bạn không phải suy nghĩ về nó nữa

Cách biểu diễn như vậy giúp loại bỏ các lỗi tràn số nguyên và tạo ảo giác về độ dài bit vô hạn, nhưng nó đòi hỏi nhiều bộ nhớ hơn. Ngoài ra, việc thực hiện số học bignum chậm hơn so với với độ chính xác cố định vì nó không thể chạy trực tiếp trong phần cứng mà không có lớp mô phỏng trung gian

Một thách thức khác là giữ hành vi nhất quán của các toán tử bit trên các loại số nguyên thay thế, điều này rất quan trọng trong việc xử lý bit dấu. Nhớ lại rằng các số nguyên có độ chính xác cố định trong Python sử dụng biểu diễn phần bù của hai tiêu chuẩn từ C, trong khi các số nguyên lớn sử dụng độ lớn của dấu hiệu

Để giảm thiểu sự khác biệt đó, Python sẽ thực hiện chuyển đổi nhị phân cần thiết cho bạn. Nó có thể thay đổi cách biểu diễn một số trước và sau khi áp dụng toán tử bitwise. Đây là một nhận xét có liên quan từ mã nguồn CPython, giải thích điều này chi tiết hơn

Các phép toán theo bit cho các số âm hoạt động như thể trên biểu diễn phần bù của hai. Vì vậy, hãy chuyển đổi các đối số từ độ lớn của dấu hiệu thành phần bù của hai và chuyển đổi kết quả trở lại độ lớn của dấu hiệu ở cuối. []

Nói cách khác, các số âm được coi là chuỗi bit bổ sung của hai khi bạn áp dụng toán tử bitwise trên chúng, mặc dù kết quả sẽ được hiển thị cho bạn ở dạng ký hiệu. Tuy nhiên, có nhiều cách và một số loại không dấu trong Python

Chuỗi bit trong Python

Bạn có thể sử dụng bút và giấy trong suốt phần còn lại của bài viết này. Nó thậm chí có thể phục vụ như một bài tập tuyệt vời. Tuy nhiên, tại một số điểm, bạn sẽ muốn xác minh xem các chuỗi nhị phân hoặc chuỗi bit của mình có tương ứng với các số dự kiến ​​trong Python hay không. Đây là cách

Chuyển đổi
>>> [42].bit_length[]
6
73 sang nhị phân

Để hiển thị các bit tạo nên một số nguyên trong Python, bạn có thể in một chuỗi ký tự được định dạng, tùy chọn này cho phép bạn chỉ định số lượng các số 0 đứng đầu sẽ hiển thị

>>>

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

Ngoài ra, bạn có thể gọi

>>> [42].bit_length[]
6
86 với số làm đối số

>>>

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

Hàm tích hợp toàn cầu này trả về một chuỗi bao gồm một ký tự nhị phân, bắt đầu bằng tiền tố

>>> [42].bit_length[]
6
87 và theo sau là các số 1 và 0. Nó luôn hiển thị số chữ số tối thiểu không có số 0 đứng đầu

Bạn cũng có thể sử dụng nguyên văn các từ như vậy trong mã của mình

>>>

if age >= 18 and not is_self_excluded:
    print["You can gamble"]
0

Các ký tự số nguyên khác có sẵn trong Python là các ký tự thập lục phân và bát phân, mà bạn có thể nhận được bằng các hàm

>>> [42].bit_length[]
6
88 và
>>> [42].bit_length[]
6
89 tương ứng

>>>

if age >= 18 and not is_self_excluded:
    print["You can gamble"]
1

Lưu ý cách hệ thập lục phân, cơ số mười sáu, tận dụng các chữ cái

>>> [42].bit_length[]
6
90 đến
>>> [42].bit_length[]
6
91 để tăng thêm tập hợp các chữ số có sẵn. Các ký tự bát phân trong các ngôn ngữ lập trình khác thường có tiền tố là số 0, điều này có thể gây nhầm lẫn. Python rõ ràng cấm những chữ như vậy để tránh mắc lỗi

>>>

if age >= 18 and not is_self_excluded:
    print["You can gamble"]
2

Bạn có thể biểu thị cùng một giá trị theo nhiều cách khác nhau bằng cách sử dụng bất kỳ số nguyên nào được đề cập

>>>

if age >= 18 and not is_self_excluded:
    print["You can gamble"]
3

Chọn cái có ý nghĩa nhất trong ngữ cảnh. Ví dụ: theo thông lệ, biểu thị bằng ký hiệu thập lục phân. Mặt khác, chữ bát phân ngày nay hiếm khi được nhìn thấy

Tất cả các chữ số trong Python đều không phân biệt chữ hoa chữ thường, vì vậy bạn có thể thêm tiền tố cho chúng bằng chữ thường hoặc chữ hoa

>>>

if age >= 18 and not is_self_excluded:
    print["You can gamble"]
4

Điều này cũng áp dụng cho các chữ số dấu phẩy động sử dụng ký hiệu khoa học cũng như các chữ số phức tạp.

Loại bỏ các quảng cáo

Chuyển đổi nhị phân thành
>>> [42].bit_length[]
6
73

Khi bạn đã sẵn sàng chuỗi bit của mình, bạn có thể lấy biểu diễn thập phân của nó bằng cách tận dụng ký tự nhị phân

>>>

if age >= 18 and not is_self_excluded:
    print["You can gamble"]
5

Đây là một cách nhanh chóng để thực hiện chuyển đổi trong khi làm việc bên trong trình thông dịch Python tương tác. Thật không may, nó sẽ không cho phép bạn chuyển đổi các chuỗi bit được tổng hợp trong thời gian chạy vì tất cả các chữ cần được mã hóa cứng trong mã nguồn

Ghi chú. Bạn có thể muốn đánh giá mã Python bằng

>>> [42].bit_length[]
6
93, nhưng đó là một cách dễ dàng làm tổn hại đến tính bảo mật của chương trình của bạn, vì vậy đừng làm điều đó

Gọi

>>> [42].bit_length[]
6
94 với hai đối số sẽ hoạt động tốt hơn trong trường hợp chuỗi bit được tạo động

>>>

if age >= 18 and not is_self_excluded:
    print["You can gamble"]
6

Đối số đầu tiên là một chuỗi các chữ số, trong khi đối số thứ hai xác định cơ sở của hệ thống số. Không giống như một chữ nhị phân, một chuỗi có thể đến từ bất cứ đâu, ngay cả khi người dùng gõ trên bàn phím. Để có cái nhìn sâu hơn về

>>> [42].bit_length[]
6
94, bạn có thể mở rộng hộp bên dưới

Các cách sử dụng khác của

>>> [42].bit_length[]
6
94Hiển thị/Ẩn

Có nhiều cách khác để gọi

>>> [42].bit_length[]
6
94. Ví dụ, nó trả về 0 khi được gọi mà không có đối số

>>>

if age >= 18 and not is_self_excluded:
    print["You can gamble"]
7

Tính năng này làm cho nó trở thành một mẫu phổ biến trong bộ sưu tập

>>> [42].bit_length[]
6
98, cần một nhà cung cấp giá trị mặc định. Lấy điều này làm ví dụ

>>>

if age >= 18 and not is_self_excluded:
    print["You can gamble"]
8

Đây,

>>> [42].bit_length[]
6
94 giúp đếm số từ trong một câu. Nó được gọi tự động bất cứ khi nào
>>> [42].bit_length[]
6
98 cần khởi tạo giá trị của khóa bị thiếu trong từ điển

Một cách sử dụng phổ biến khác của

>>> [42].bit_length[]
6
94 là đánh máy. Ví dụ: khi bạn vượt qua
>>> [42].bit_length[]
6
94 một giá trị dấu phẩy động, nó sẽ cắt bớt giá trị bằng cách loại bỏ thành phần phân số

>>>

if age >= 18 and not is_self_excluded:
    print["You can gamble"]
9

Khi bạn đưa cho nó một chuỗi, nó sẽ cố phân tích một số từ chuỗi đó

>>>

if age >= 18 & ~is_self_excluded:
    print["You can gamble"]
0

Nói chung,

>>> [42].bit_length[]
6
94 sẽ chấp nhận một đối tượng thuộc bất kỳ loại nào miễn là nó xác định một phương thức đặc biệt có thể xử lý việc chuyển đổi

Càng xa càng tốt. Nhưng còn số âm thì sao?

Mô phỏng bit dấu hiệu

Khi bạn gọi

>>> [42].bit_length[]
6
86 trên một số nguyên âm, nó chỉ thêm dấu trừ vào chuỗi bit thu được từ giá trị dương tương ứng

>>>

if age >= 18 & ~is_self_excluded:
    print["You can gamble"]
1

Thay đổi dấu của một số không ảnh hưởng đến chuỗi bit cơ bản trong Python. Ngược lại, bạn được phép đặt trước một chuỗi bit bằng dấu trừ khi chuyển đổi nó sang dạng thập phân

>>>

if age >= 18 & ~is_self_excluded:
    print["You can gamble"]
2

Điều đó có ý nghĩa trong Python bởi vì, bên trong, nó không sử dụng bit dấu. Bạn có thể nghĩ về dấu của một số nguyên trong Python như một phần thông tin được lưu trữ tách biệt với mô đun

Tuy nhiên, có một vài cách giải quyết cho phép bạn mô phỏng các chuỗi bit có độ dài cố định chứa bit dấu

  • Bitmask
  • Hoạt động mô-đun [
    >>> [42].bit_length[]
    6
    
    39]
  • Mô-đun
    >>> [42].bit_length[]
    6
    
    58
  • Mô-đun
    >>> len["€uro".encode["utf-8"]]
    6
    
    07
  • Mô-đun
    >>> len["€uro".encode["utf-8"]]
    6
    
    08

Bạn đã biết từ các phần trước rằng để đảm bảo độ dài bit nhất định của một số, bạn có thể sử dụng mặt nạ bit tiện lợi. Ví dụ: để giữ một byte, bạn có thể sử dụng mặt nạ bao gồm chính xác tám bit được bật

>>>

if age >= 18 & ~is_self_excluded:
    print["You can gamble"]
3

Mặt nạ buộc Python tạm thời thay đổi biểu diễn của số từ độ lớn của dấu thành phần bù của hai và sau đó quay lại lần nữa. Nếu bạn quên giá trị thập phân của chữ nhị phân kết quả, bằng 21410, thì nó sẽ biểu thị -4210 trong phần bù hai. Bit ngoài cùng bên trái sẽ là bit dấu

Ngoài ra, bạn có thể tận dụng phép toán modulo mà bạn đã sử dụng trước đây để mô phỏng phép dịch phải logic trong Python

>>>

if age >= 18 & ~is_self_excluded:
    print["You can gamble"]
4

Nếu điều đó có vẻ quá phức tạp đối với sở thích của bạn, thì bạn có thể sử dụng một trong các mô-đun từ thư viện tiêu chuẩn thể hiện rõ ràng hơn cùng một mục đích. For example, using

>>> [42].bit_length[]
6
58 will have an identical effect

>>>

if age >= 18 & ~is_self_excluded:
    print["You can gamble"]
5

Bạn đã từng thấy nó trước đây, nhưng xin nhắc lại, nó sẽ loại bỏ các kiểu số nguyên không dấu từ C

Another standard module that you can use for this kind of conversion in Python is the

>>> len["€uro".encode["utf-8"]]
6
07 module. It defines a data structure that’s similar to a
>>> len["€uro".encode["utf-8"]]
6
11 but is only allowed to hold elements of the same numeric type. When declaring an array, you need to indicate its type up front with a corresponding letter

>>>

if age >= 18 & ~is_self_excluded:
    print["You can gamble"]
6

For example,

>>> len["€uro".encode["utf-8"]]
6
12 stands for an 8-bit signed byte, while
>>> len["€uro".encode["utf-8"]]
6
13 stands for its unsigned equivalent. There are a few other predefined types, such as a signed 16-bit integer or a 32-bit floating-point number

Copying raw bytes between these two arrays changes how bits are interpreted. However, it takes twice the amount of memory, which is quite wasteful. To perform such a bit rewriting in place, you can rely on the

>>> len["€uro".encode["utf-8"]]
6
08 module, which uses a similar set of for type declarations

>>>

if age >= 18 & ~is_self_excluded:
    print["You can gamble"]
7

Packing lets you lay objects in memory according to the given C data type specifiers. It returns a read-only object, which contains raw bytes of the resulting block of memory. Later, you can read back those bytes using a different set of type codes to change how they’re translated into Python objects

Up to this point, you’ve used different techniques to obtain fixed-length bit strings of integers expressed in two’s complement representation. If you want to convert these types of bit sequences back to Python integers instead, then you can try this function

if age >= 18 & ~is_self_excluded:
    print["You can gamble"]
8

The function accepts a string composed of binary digits. First, it converts the digits to a plain unsigned integer, disregarding the sign bit. Next, it uses two bitmasks to extract the sign and magnitude bits, whose locations depend on the specified bit-length. Finally, it combines them using regular arithmetic, knowing that the value associated with the sign bit is negative

You can try it out against the trusty old bit string from earlier examples

>>>

if age >= 18 & ~is_self_excluded:
    print["You can gamble"]
9

Python’s

>>> [42].bit_length[]
6
94 treats all the bits as the magnitude, so there are no surprises there. However, this new function assumes a 32-bit long string by default, which means the sign bit is implicitly equal to zero for shorter strings. When you request a bit-length that matches your bit string, then you’ll get the expected result

While integer is the most appropriate data type for working with bitwise operators in most cases, you’ll sometimes need to extract and manipulate fragments of structured binary data, such as image pixels. The

>>> len["€uro".encode["utf-8"]]
6
07 and
>>> len["€uro".encode["utf-8"]]
6
08 modules briefly touch upon this topic, so you’ll explore it in more detail next

Seeing Data in Binary

You know how to read and interpret individual bytes. However, real-world data often consists of more than one byte to convey information. Take the

>>> [42].bit_length[]
6
68 data type as an example. A single floating-point number in Python occupies as many as eight bytes in memory

How do you see those bytes?

You can’t simply use bitwise operators because they don’t work with floating-point numbers

>>>

>>> age = 18
>>> is_self_excluded = True
>>> age >= 18 & ~is_self_excluded  # Bitwise logical operators
True
>>> age >= 18 and not is_self_excluded  # Logical operators
False
0

You have to forget about the particular data type you’re dealing with and think of it in terms of a generic stream of bytes. That way, it won’t matter what the bytes represent outside the context of them being processed by the bitwise operators

To get the

>>> len["€uro".encode["utf-8"]]
6
15 of a floating-point number in Python, you can pack it using the familiar
>>> len["€uro".encode["utf-8"]]
6
08 module

>>>

>>> age = 18
>>> is_self_excluded = True
>>> age >= 18 & ~is_self_excluded  # Bitwise logical operators
True
>>> age >= 18 and not is_self_excluded  # Logical operators
False
1

Ignore the format characters passed through the first argument. They won’t make sense until you get to the section below. Đằng sau cách trình bày văn bản khá tối nghĩa này ẩn chứa một danh sách tám số nguyên

>>>

>>> age = 18
>>> is_self_excluded = True
>>> age >= 18 & ~is_self_excluded  # Bitwise logical operators
True
>>> age >= 18 and not is_self_excluded  # Logical operators
False
2

Their values correspond to the subsequent bytes used to represent a floating-point number in binary. You can combine them to produce a very long bit string

>>>

>>> age = 18
>>> is_self_excluded = True
>>> age >= 18 & ~is_self_excluded  # Bitwise logical operators
True
>>> age >= 18 and not is_self_excluded  # Logical operators
False
3

These 64 bits are the sign, exponent, and mantissa in double precision that you read about earlier. To synthesize a

>>> [42].bit_length[]
6
68 from a similar bit string, you can reverse the process

>>>

>>> age = 18
>>> is_self_excluded = True
>>> age >= 18 & ~is_self_excluded  # Bitwise logical operators
True
>>> age >= 18 and not is_self_excluded  # Logical operators
False
4

>>> len["€uro".encode["utf-8"]]
6
23 returns a tuple because it allows you to read more than one value at a time. For example, you could read the same bit string as four 16-bit signed integers

>>>

>>> age = 18
>>> is_self_excluded = True
>>> age >= 18 & ~is_self_excluded  # Bitwise logical operators
True
>>> age >= 18 and not is_self_excluded  # Logical operators
False
5

As you can see, the way a bit string should be interpreted must be known up front to avoid ending up with garbled data. One important question you need to ask yourself is which end of the byte stream you should start reading from—left or right. Read on to find out

Byte Order

There’s no dispute about the order of bits in a single byte. You’ll always find the least-significant bit at index zero and the most-significant bit at index seven, regardless of how they’re physically laid out in memory. The bitwise shift operators rely on this consistency

However, there’s no consensus for the byte order in multibyte chunks of data. A piece of information comprising more than one byte can be read from left to right like an English text or from right to left like an Arabic one, for example. Computers see bytes in a binary stream like humans see words in a sentence

Việc máy tính chọn đọc byte từ hướng nào không quan trọng miễn là chúng áp dụng các quy tắc giống nhau ở mọi nơi. Unfortunately, different computer architectures use different approaches, which makes transferring data between them challenging

Big-Endian vs Little-Endian

Let’s take a 32-bit unsigned integer corresponding to the number 196910, which was the year when Monty Python first appeared on TV. With all the leading zeros, it has the following binary representation 000000000000000000000111101100012

How would you store such a value in computer memory?

If you imagine memory as a one-dimensional tape consisting of bytes, then you’d need to break that data down into individual bytes and arrange them in a contiguous block. Some find it natural to start from the left end because that’s how they read, while others prefer starting at the right end

Byte OrderAddress NAddress N+1Address N+2Address N+3Big-Endian000000002000000002000001112101100012Little-Endian101100012000001112000000002000000002

When bytes are placed from left to right, the most-significant byte is assigned to the lowest memory address. This is known as the big-endian order. Conversely, when bytes are stored from right to left, the least-significant byte comes first. That’s called little-endian order

Note. These humorous names draw inspiration from the eighteenth-century novel Gulliver’s Travels by Jonathan Swift. The author describes a conflict between the Little-Endians and the Big-Endians over the correct way to break the shell of a boiled egg. While Little-Endians prefer to start with the little pointy end, Big-Endians like the big end more

Which way is better?

From a practical standpoint, there’s no real advantage of using one over the other. There might be some marginal gains in performance at the hardware level, but you won’t notice them. Major network protocols use the big-endian order, which allows them to filter data packets more quickly given the hierarchical design of IP addressing. Other than that, some people may find it more convenient to work with a particular byte order when debugging

Either way, if you don’t get it right and mix up the two standards, then bad things start to happen

>>>

>>> age = 18
>>> is_self_excluded = True
>>> age >= 18 & ~is_self_excluded  # Bitwise logical operators
True
>>> age >= 18 and not is_self_excluded  # Logical operators
False
6

When you serialize some value to a stream of bytes using one convention and try reading it back with another, you’ll get a completely useless result. This scenario is most likely when data is sent over a network, but you can also experience it when reading a local file in a specific format. For example, the header of a Windows bitmap always uses little-endian, while JPEG can use both byte orders

Native Endianness

To find out your platform’s endianness, you can use the

>>> len["€uro".encode["utf-8"]]
6
24 module

>>>

>>> age = 18
>>> is_self_excluded = True
>>> age >= 18 & ~is_self_excluded  # Bitwise logical operators
True
>>> age >= 18 and not is_self_excluded  # Logical operators
False
7

You can’t change endianness, though, because it’s an intrinsic feature of your CPU architecture. It’s impossible to mock it for testing purposes without hardware virtualization such as QEMU, so even the popular VirtualBox won’t help

Notably, the x86 family of processors from Intel and AMD, which power most modern laptops and desktops, are little-endian. Các thiết bị di động dựa trên kiến ​​trúc ARM năng lượng thấp, trong khi một số kiến ​​trúc cũ hơn như Motorola 68000 cổ đại chỉ dành cho thiết bị lớn.

For information on determining endianness in C, expand the box below

Checking the Byte Order in CShow/Hide

Historically, the way to get your machine’s endianness in C was to declare a small integer and then read its first byte with a pointer

>>> age = 18
>>> is_self_excluded = True
>>> age >= 18 & ~is_self_excluded  # Bitwise logical operators
True
>>> age >= 18 and not is_self_excluded  # Logical operators
False
8

Nếu giá trị xuất hiện cao hơn 0, thì byte được lưu trữ ở địa chỉ bộ nhớ thấp nhất phải là byte có ý nghĩa nhỏ nhất

Once you know the native endianness of your machine, you’ll want to convert between different byte orders when manipulating binary data. A universal way to do so, regardless of the data type at hand, is to reverse a generic

>>> len["€uro".encode["utf-8"]]
6
15 object or a sequence of integers representing those bytes

>>>

>>> age = 18
>>> is_self_excluded = True
>>> age >= 18 & ~is_self_excluded  # Bitwise logical operators
True
>>> age >= 18 and not is_self_excluded  # Logical operators
False
9

However, it’s often more convenient to use the

>>> len["€uro".encode["utf-8"]]
6
08 module, which lets you define standard C data types. In addition to this, it allows you to request a given byte order with an optional modifier

>>>

>>> age >= [18 & ~is_self_excluded]
True
0

The greater-than sign [

>>> len["€uro".encode["utf-8"]]
6
27] indicates that bytes are laid out in the big-endian order, while the less-than symbol [
>>> len["€uro".encode["utf-8"]]
6
28] corresponds to little-endian. If you don’t specify one, then native endianness is assumed. There are a few more modifiers, like the exclamation mark [
>>> len["€uro".encode["utf-8"]]
6
29], which signifies the network byte order

Network Byte Order

Computer networks are made of heterogeneous devices such as laptops, desktops, tablets, smartphones, and even light bulbs equipped with a Wi-Fi adapter. They all need agreed-upon protocols and standards, including the byte order for binary transmission, to communicate effectively

At the dawn of the Internet, it was decided that the byte order for those network protocols would be big-endian

Programs that want to communicate over a network can grab the classic C API, which abstracts away the nitty-gritty details with a socket layer. Python wraps that API through the built-in

>>> len["€uro".encode["utf-8"]]
6
30 module. However, unless you’re writing a custom binary protocol, you’ll probably want to take advantage of an even higher-level abstraction, such as the HTTP protocol, which is text-based

Where the

>>> len["€uro".encode["utf-8"]]
6
30 module can be useful is in the byte order conversion. It exposes a few functions from the C API, with their distinctive, tongue-twisting names

>>>

>>> age >= [18 & ~is_self_excluded]
True
1

If your host already uses the big-endian byte order, then there’s nothing to be done. The values will remain the same

Bitmasks

A bitmask works like a graffiti stencil that blocks the paint from being sprayed on particular areas of a surface. It lets you isolate the bits to apply some function on them selectively. Bitmasking involves both the bitwise logical operators and the bitwise shift operators that you’ve read about

You can find bitmasks in a lot of different contexts. For example, the subnet mask in IP addressing is actually a bitmask that helps you extract the network address. Pixel channels, which correspond to the red, green, and blue colors in the RGB model, can be accessed with a bitmask. You can also use a bitmask to define Boolean flags that you can then pack on a bit field

There are a few common types of operations associated with bitmasks. You’ll take a quick look at some of them below

Getting a Bit

To read the value of a particular bit on a given position, you can use the bitwise AND against a bitmask composed of only one bit at the desired index

>>>

>>> age >= [18 & ~is_self_excluded]
True
2

The mask will suppress all bits except for the one that you’re interested in. It’ll result in either zero or a power of two with an exponent equal to the bit index. If you’d like to get a simple yes-or-no answer instead, then you could shift to the right and check the least-significant bit

>>>

>>> age >= [18 & ~is_self_excluded]
True
3

Lần này, nó sẽ chuẩn hóa giá trị bit để nó không bao giờ vượt quá một. You could then use that function to derive a Boolean

>>> [42].bit_length[]
6
28 or
>>> [42].bit_length[]
6
29 value rather than a numeric value

Setting a Bit

Setting a bit is similar to getting one. You take advantage of the same bitmask as before, but instead of using bitwise AND, you use the bitwise OR operator

>>>

>>> age >= [18 & ~is_self_excluded]
True
4

The mask retains all the original bits while enforcing a binary one at the specified index. Had that bit already been set, its value wouldn’t have changed

Unsetting a Bit

To clear a bit, you want to copy all binary digits while enforcing zero at one specific index. You can achieve this effect by using the same bitmask once again, but in the inverted form

>>>

>>> age >= [18 & ~is_self_excluded]
True
5

Using the bitwise NOT on a positive number always produces a negative value in Python. While this is generally undesirable, it doesn’t matter here because you immediately apply the bitwise AND operator. This, in turn, triggers the mask’s conversion to two’s complement representation, which gets you the expected result

Toggling a Bit

Sometimes it’s useful to be able to toggle a bit on and off again periodically. Đó là một cơ hội hoàn hảo cho toán tử XOR bitwise, có thể lật ngược bit của bạn như thế

>>>

>>> age >= [18 & ~is_self_excluded]
True
6

Notice the same bitmask being used again. A binary one on the specified position will make the bit at that index invert its value. Having binary zeros on the remaining places will ensure that the rest of the bits will be copied

Bitwise Operator Overloading

The primary domain of bitwise operators is integer numbers. That’s where they make the most sense. However, you’ve also seen them used in a Boolean context, in which they replaced the logical operators. Python provides alternative implementations for some of its operators and lets you overload them for new data types

Mặc dù đề xuất quá tải các toán tử logic trong Python đã bị từ chối, nhưng bạn có thể đưa ra ý nghĩa mới cho bất kỳ toán tử theo bit nào. Nhiều thư viện phổ biến và thậm chí cả thư viện tiêu chuẩn, tận dụng lợi thế của nó

Các kiểu dữ liệu tích hợp

Các toán tử bitwise trong Python được xác định cho các kiểu dữ liệu tích hợp sau

  • >>> [42].bit_length[]
    6
    
    73
  • >>> len["€uro".encode["utf-8"]]
    6
    
    35
  • >>> len["€uro".encode["utf-8"]]
    6
    
    36 và
  • >>> len["€uro".encode["utf-8"]]
    6
    
    38 [kể từ Python 3. 9]

Đó không phải là một thực tế được biết đến rộng rãi, nhưng các toán tử bitwise có thể thực hiện các phép toán từ đại số tập hợp, chẳng hạn như hợp, giao và hiệu đối xứng, cũng như hợp nhất và cập nhật từ điển

Ghi chú. Tại thời điểm viết bài, Python 3. 9 chưa được phát hành, nhưng bạn có thể xem trước các tính năng ngôn ngữ sắp tới bằng cách sử dụng Docker hoặc pyenv

Khi

>>> [42].bit_length[]
6
31 và
>>> [42].bit_length[]
6
32 là các bộ Python, thì các toán tử bitwise tương ứng với các phương thức sau

Set MethodBitwise Operator

>>> len["€uro".encode["utf-8"]]
6
41
>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
4
>>> len["€uro".encode["utf-8"]]
6
43
>>> [42].bit_length[]
6
09
>>> len["€uro".encode["utf-8"]]
6
45
>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
2
>>> len["€uro".encode["utf-8"]]
6
47
>>> [42].bit_length[]
6
06
>>> len["€uro".encode["utf-8"]]
6
49
>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
6
>>> len["€uro".encode["utf-8"]]
6
51
>>> [42].bit_length[]
6
12

Chúng hầu như làm cùng một việc, vì vậy việc sử dụng cú pháp nào là tùy thuộc vào bạn. Ngoài ra, còn có một toán tử trừ đã quá tải [

>>> [42].bit_length[]
6
64], thực hiện hiệu của hai tập hợp. Để xem chúng hoạt động như thế nào, giả sử bạn có hai nhóm trái cây và rau củ sau đây

>>>

>>> age >= [18 & ~is_self_excluded]
True
7

Họ chia sẻ một thành viên chung, rất khó để phân loại, nhưng phần còn lại của các yếu tố của họ là rời rạc

Một điều cần chú ý là

>>> len["€uro".encode["utf-8"]]
6
54 bất biến, thiếu các phương thức cập nhật tại chỗ. Tuy nhiên, khi bạn sử dụng các đối tác toán tử bitwise của chúng, ý nghĩa hơi khác một chút

>>>

>>> age >= [18 & ~is_self_excluded]
True
8

Rốt cuộc, có vẻ như

>>> len["€uro".encode["utf-8"]]
6
54 không phải là bất biến khi bạn sử dụng các toán tử bitwise, nhưng vấn đề nằm ở chi tiết. Đây là những gì thực sự xảy ra

>>> age >= [18 & ~is_self_excluded]
True
9

Lý do nó hoạt động lần thứ hai là bạn không thay đổi đối tượng bất biến ban đầu. Thay vào đó, bạn tạo một cái mới và gán lại nó cho cùng một biến

Python

>>> len["€uro".encode["utf-8"]]
6
38 chỉ hỗ trợ theo bit HOẶC, hoạt động giống như toán tử hợp. Bạn có thể sử dụng nó để cập nhật một từ điển tại chỗ hoặc hợp nhất hai từ điển thành một từ điển mới

>>>

>>> [age >= 18] & ~is_self_excluded
0
0

Phiên bản tăng cường của toán tử bitwise tương đương với

>>> len["€uro".encode["utf-8"]]
6
57

Mô-đun của bên thứ ba

Nhiều thư viện phổ biến, bao gồm NumPy, pandas và SQLAlchemy, làm quá tải các toán tử bitwise cho các kiểu dữ liệu cụ thể của chúng. Đây là nơi rất có thể bạn sẽ tìm thấy các toán tử bitwise trong Python vì chúng không còn được sử dụng thường xuyên theo nghĩa gốc của chúng nữa

Ví dụ: NumPy áp dụng chúng cho dữ liệu được vector hóa theo kiểu điểm

>>>

>>> [age >= 18] & ~is_self_excluded
0
1

Bằng cách này, bạn không cần phải áp dụng thủ công cùng một toán tử từng bit cho từng phần tử của mảng. Nhưng bạn không thể làm điều tương tự với danh sách thông thường trong Python

pandas sử dụng NumPy đằng sau hậu trường và nó cũng cung cấp các phiên bản quá tải của toán tử bitwise cho các đối tượng

>>> len["€uro".encode["utf-8"]]
6
58 và
>>> len["€uro".encode["utf-8"]]
6
59 của nó. Tuy nhiên, họ cư xử như bạn mong đợi. Sự khác biệt duy nhất là chúng thực hiện công việc thông thường của chúng trên các vectơ và ma trận của các số thay vì trên các đại lượng vô hướng riêng lẻ

Things get more interesting with libraries that give the bitwise operators entirely new meanings. For example, SQLAlchemy provides a compact syntax for querying the database

>>> [age >= 18] & ~is_self_excluded
0
2

The bitwise AND operator [

>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
1] will eventually translate to a piece of SQL query. However, that’s not very obvious, at least not to my IDE, which complains about the unpythonic use of bitwise operators when it sees them in this type of expression. It immediately suggests replacing every occurrence of
>>> def call[x]:
..     print[f"call[{x=}]"]
..     return x
...
>>> call[False] or call[True]  # Both operands evaluated
call[x=False]
call[x=True]
True
>>> call[True] or call[False]  # Only the left operand evaluated
call[x=True]
True
1 with a logical
>>> [42].bit_length[]
6
25, not knowing that doing so would make the code stop working

This type of operator overloading is a controversial practice that relies on implicit magic you have to know up front. Some programming languages like Java prevent such abuse by disallowing operator overloading altogether. Python is more liberal in that regard and trusts that you know what you’re doing

Custom Data Types

To customize the behavior of Python’s bitwise operators, you have to define a class and then implement the corresponding in it. At the same time, you can’t redefine the behavior of the bitwise operators for the existing types. Operator overloading is possible only on new data types

Here’s a quick rundown of special methods that let you overload the bitwise operators

Magic MethodExpression

>>> len["€uro".encode["utf-8"]]
6
63
>>> len["€uro".encode["utf-8"]]
6
64
>>> len["€uro".encode["utf-8"]]
6
65
>>> len["€uro".encode["utf-8"]]
6
66
>>> len["€uro".encode["utf-8"]]
6
67
>>> len["€uro".encode["utf-8"]]
6
68
>>> len["€uro".encode["utf-8"]]
6
69
>>> len["€uro".encode["utf-8"]]
6
70
>>> len["€uro".encode["utf-8"]]
6
71
>>> len["€uro".encode["utf-8"]]
6
72
>>> len["€uro".encode["utf-8"]]
6
73
>>> len["€uro".encode["utf-8"]]
6
74
>>> len["€uro".encode["utf-8"]]
6
75
>>> len["€uro".encode["utf-8"]]
6
76
>>> len["€uro".encode["utf-8"]]
6
77
>>> len["€uro".encode["utf-8"]]
6
78
>>> len["€uro".encode["utf-8"]]
6
79
>>> len["€uro".encode["utf-8"]]
6
80
>>> len["€uro".encode["utf-8"]]
6
81
>>> len["€uro".encode["utf-8"]]
6
82
>>> len["€uro".encode["utf-8"]]
6
83
>>> len["€uro".encode["utf-8"]]
6
84
>>> len["€uro".encode["utf-8"]]
6
85
>>> len["€uro".encode["utf-8"]]
6
86
>>> len["€uro".encode["utf-8"]]
6
87
>>> len["€uro".encode["utf-8"]]
6
88
>>> len["€uro".encode["utf-8"]]
6
89
>>> len["€uro".encode["utf-8"]]
6
90
>>> len["€uro".encode["utf-8"]]
6
91
>>> len["€uro".encode["utf-8"]]
6
92
>>> len["€uro".encode["utf-8"]]
6
93
>>> len["€uro".encode["utf-8"]]
6
94

You don’t need to define all of them. For example, to have a slightly more convenient syntax for appending and prepending elements to a , it’s sufficient to implement only

>>> len["€uro".encode["utf-8"]]
6
95 and
>>> len["€uro".encode["utf-8"]]
6
96

>>>

>>> [age >= 18] & ~is_self_excluded
0
3

This user-defined class wraps a deque to reuse its implementation and augment it with two additional methods that allow for adding items to the left or right end of the collection

Least-Significant Bit Steganography

Whew, that was a lot to process. If you’re still scratching your head, wondering why you’d want to use bitwise operators, then don’t worry. It’s time to showcase what you can do with them in a fun way

To follow along with the examples in this section, you can download the source code by clicking the link below

Lấy mã nguồn. Nhấp vào đây để lấy mã nguồn mà bạn sẽ sử dụng để tìm hiểu về các toán tử bitwise của Python trong hướng dẫn này

You’re going to learn about steganography and apply this concept to secretly embed arbitrary files in bitmap images

Cryptography vs Steganography

Cryptography is about changing a message into one that is readable only to those with the right key. Everyone else can still see the encrypted message, but it won’t make any sense to them. One of the first forms of cryptography was the substitution cipher, such as the named after Julius Caesar

Steganography is similar to cryptography because it also allows you to share secret messages with your desired audience. However, instead of using encryption, it cleverly hides information in a medium that doesn’t attract attention. Examples include using invisible ink or writing an acrostic in which the first letter of every word or line forms a secret message

Unless you knew that a secret message was concealed and the method to recover it, you’d probably ignore the carrier. You can combine both techniques to be even safer, hiding an encrypted message rather than the original one

There are plenty of ways to smuggle secret data in the digital world. In particular, file formats carrying lots of data, such as audio files, videos, or images, are a great fit because they give you a lot of room to work with. Ví dụ, các công ty phát hành tài liệu có bản quyền có thể sử dụng kỹ thuật ghi ảnh để đánh dấu các bản sao riêng lẻ và truy tìm nguồn rò rỉ

Below, you’ll inject secret data into a plain bitmap, which is straightforward to read and write in Python without the need for external dependencies

Bitmap File Format

The word bitmap usually refers to the Windows bitmap [

>>> len["€uro".encode["utf-8"]]
6
97] file format, which supports a few alternative ways of representing pixels. To make life easier, you’re going to assume that pixels are stored in 24-bit uncompressed RGB [red, green, and blue] format. A pixel will have three color channels that can each hold values from 010 to 25510

Every bitmap begins with a file header, which contains metadata such as the image width and height. Here are a few interesting fields and their positions relative to the start of the header

FieldByte OffsetBytes LengthTypeSample ValueSignature

>>> len["€uro".encode["utf-8"]]
6
982StringBMFile Size
>>> len["€uro".encode["utf-8"]]
6
994Unsigned int7,629,186Reserved #1
>>> for char in "€uro":
..     print[char, len[char.encode["utf-8"]]]
...
€ 3
u 1
r 1
o 1
002Bytes0Reserved #2
>>> for char in "€uro":
..     print[char, len[char.encode["utf-8"]]]
...
€ 3
u 1
r 1
o 1
012Bytes0Pixels Offset
>>> for char in "€uro":
..     print[char, len[char.encode["utf-8"]]]
...
€ 3
u 1
r 1
o 1
024Unsigned int122Pixels Size
>>> for char in "€uro":
..     print[char, len[char.encode["utf-8"]]]
...
€ 3
u 1
r 1
o 1
034Unsigned int7,629,064Image Width
>>> for char in "€uro":
..     print[char, len[char.encode["utf-8"]]]
...
€ 3
u 1
r 1
o 1
044Unsigned int1,954Image Height
>>> for char in "€uro":
..     print[char, len[char.encode["utf-8"]]]
...
€ 3
u 1
r 1
o 1
054Unsigned int1,301Bits Per Pixel
>>> for char in "€uro":
..     print[char, len[char.encode["utf-8"]]]
...
€ 3
u 1
r 1
o 1
062Unsigned short24Compression
>>> for char in "€uro":
..     print[char, len[char.encode["utf-8"]]]
...
€ 3
u 1
r 1
o 1
074Unsigned int0Colors Palette
>>> for char in "€uro":
..     print[char, len[char.encode["utf-8"]]]
...
€ 3
u 1
r 1
o 1
084Unsigned int0

You can infer from this header that the corresponding bitmap is 1,954 pixels wide and 1,301 pixels high. It doesn’t use compression, nor does it have a color palette. Every pixel occupies 24 bits, or 3 bytes, and the raw pixel data starts at offset 12210

You can open the bitmap in binary mode, seek the desired offset, read the given number of bytes, and deserialize them using

>>> len["€uro".encode["utf-8"]]
6
08 like before

>>> [age >= 18] & ~is_self_excluded
0
4

Note that all integer fields in bitmaps are stored in the little-endian byte order

You might have noticed a small discrepancy between the number of pixel bytes declared in the header and the one that would result from the image size. When you multiply 1,954 pixels × 1,301 pixels × 3 bytes, you get a value that is 2,602 bytes less than 7,629,064

This is because pixel bytes are padded with zeros so that every row is a multiple of four bytes. Nếu chiều rộng của hình ảnh nhân với ba byte là bội số của bốn thì không cần đệm. Mặt khác, các byte trống được thêm vào cuối mỗi hàng

Note. Để tránh gây nghi ngờ, bạn cần tính đến phần đệm đó bằng cách bỏ qua các byte trống. Nếu không, nó sẽ là một món quà rõ ràng cho người biết phải tìm gì

Bitmap lưu trữ các hàng pixel lộn ngược, bắt đầu từ dưới cùng thay vì trên cùng. Ngoài ra, mọi pixel được tuần tự hóa thành một vectơ kênh màu theo thứ tự BGR hơi kỳ quặc thay vì RGB. Tuy nhiên, điều này không liên quan đến nhiệm vụ che giấu dữ liệu bí mật

Bitwise Ẩn và Tìm kiếm

Bạn có thể sử dụng toán tử bitwise để trải rộng dữ liệu tùy chỉnh trên các byte pixel liên tiếp. Ý tưởng là ghi đè bit ít quan trọng nhất trong mỗi bit bằng các bit đến từ byte bí mật tiếp theo. Điều này sẽ tạo ra ít nhiễu nhất, nhưng bạn có thể thử nghiệm thêm nhiều bit hơn để đạt được sự cân bằng giữa kích thước của dữ liệu được đưa vào và độ méo pixel

Ghi chú. Sử dụng chức năng ghi bit ít quan trọng nhất không ảnh hưởng đến kích thước tệp của ảnh bitmap thu được. Nó sẽ giữ nguyên như tệp gốc

Trong một số trường hợp, các bit tương ứng sẽ giống nhau, dẫn đến không có thay đổi nào về giá trị pixel. Tuy nhiên, ngay cả trong trường hợp xấu nhất, màu pixel sẽ chỉ khác một phần trăm. Một sự bất thường nhỏ như vậy sẽ không thể nhìn thấy bằng mắt người nhưng có thể được phát hiện bằng phân tích ẩn, sử dụng số liệu thống kê

Hãy xem những hình ảnh cắt này

Hình bên trái lấy từ bitmap gốc, trong khi hình ảnh bên phải mô tả một bitmap đã xử lý với video nhúng được lưu trữ trên các bit ít quan trọng nhất. Bạn có thể nhận ra sự khác biệt?

Đoạn mã sau mã hóa dữ liệu bí mật vào bitmap

>>> [age >= 18] & ~is_self_excluded
0
5

Đối với mỗi byte dữ liệu bí mật và tám byte dữ liệu pixel tương ứng, không bao gồm các byte đệm, nó chuẩn bị một danh sách các bit sẽ được trải rộng. Tiếp theo, nó ghi đè lên bit ít quan trọng nhất trong mỗi tám byte bằng cách sử dụng mặt nạ bit có liên quan. Kết quả được chuyển đổi thành một đối tượng

>>> len["€uro".encode["utf-8"]]
6
15 và được gán trở lại phần bitmap ban đầu của nó

Để giải mã một tệp từ cùng một bitmap, bạn cần biết có bao nhiêu byte bí mật đã được ghi vào đó. Bạn có thể phân bổ một vài byte ở đầu luồng dữ liệu để lưu trữ số này hoặc bạn có thể sử dụng các trường dành riêng từ tiêu đề bitmap

>>> [age >= 18] & ~is_self_excluded
0
6

Thao tác này chuyển sang phần bù bên phải trong tệp, tuần tự hóa Python

>>> [42].bit_length[]
6
73 thành byte thô và ghi chúng xuống

Bạn cũng có thể muốn lưu trữ tên của tệp bí mật của mình. Vì nó có thể có độ dài tùy ý, nên tuần tự hóa nó bằng cách sử dụng chuỗi kết thúc null, sẽ đứng trước nội dung tệp. Để tạo một chuỗi như vậy, bạn cần mã hóa một đối tượng Python

>>> for char in "€uro":
..     print[char, len[char.encode["utf-8"]]]
...
€ 3
u 1
r 1
o 1
12 thành byte và nối thêm byte rỗng ở cuối theo cách thủ công

>>>

>>> [age >= 18] & ~is_self_excluded
0
7

Ngoài ra, việc loại bỏ thư mục mẹ dư thừa khỏi đường dẫn bằng cách sử dụng

>>> for char in "€uro":
..     print[char, len[char.encode["utf-8"]]]
...
€ 3
u 1
r 1
o 1
13 cũng không hại gì

Mã mẫu bổ sung cho bài viết này sẽ cho phép bạn mã hóa, giải mã và xóa một tệp bí mật khỏi ảnh bitmap đã cho bằng các lệnh sau

>>> [age >= 18] & ~is_self_excluded
0
8

Đây là một mô-đun có thể chạy được, có thể được thực thi bằng cách gọi thư mục bao gồm của nó. Bạn cũng có thể tạo một kho lưu trữ định dạng ZIP di động từ nội dung của nó để tận dụng hỗ trợ ứng dụng Python ZIP

Chương trình này dựa trên các mô-đun từ thư viện chuẩn được đề cập trong bài viết và một số mô-đun khác mà bạn có thể chưa từng nghe đến trước đây. Một mô-đun quan trọng là

>>> for char in "€uro":
..     print[char, len[char.encode["utf-8"]]]
...
€ 3
u 1
r 1
o 1
14, hiển thị giao diện Python cho các tệp ánh xạ bộ nhớ. Chúng cho phép bạn thao tác với các tệp lớn bằng cả API tệp tiêu chuẩn và API trình tự. Như thể tệp là một danh sách lớn có thể thay đổi mà bạn có thể cắt

Hãy tiếp tục và chơi xung quanh với bitmap được đính kèm với các tài liệu hỗ trợ. Nó chứa một chút ngạc nhiên cho bạn

Phần kết luận

Nắm vững các toán tử bitwise Python mang lại cho bạn sự tự do tối đa để thao tác dữ liệu nhị phân trong các dự án của bạn. Bây giờ bạn đã biết cú pháp của chúng và các hương vị khác nhau cũng như các kiểu dữ liệu hỗ trợ chúng. Bạn cũng có thể tùy chỉnh hành vi của họ cho nhu cầu của riêng bạn

Trong hướng dẫn này, bạn đã học cách

  • Sử dụng các toán tử bitwise Python để thao tác các bit riêng lẻ
  • Đọc và ghi dữ liệu nhị phân theo cách bất khả tri trên nền tảng
  • Sử dụng bitmasks để đóng gói thông tin trên một byte đơn
  • Quá tải toán tử bitwise Python trong các loại dữ liệu tùy chỉnh
  • Ẩn tin nhắn bí mật trong hình ảnh kỹ thuật số

Bạn cũng đã học cách máy tính sử dụng hệ thống nhị phân để biểu diễn các loại thông tin kỹ thuật số khác nhau. Bạn đã thấy một số cách phổ biến để diễn giải các bit và cách giảm thiểu việc thiếu các kiểu dữ liệu không dấu trong Python cũng như cách lưu trữ số nguyên duy nhất của Python trong bộ nhớ

Với thông tin này, bạn đã sẵn sàng sử dụng toàn bộ dữ liệu nhị phân trong mã của mình. Để tải xuống mã nguồn được sử dụng trong ví dụ thủy ấn và tiếp tục thử nghiệm với các toán tử bitwise, bạn có thể nhấp vào liên kết bên dưới

Lấy mã nguồn. Nhấp vào đây để lấy mã nguồn mà bạn sẽ sử dụng để tìm hiểu về các toán tử bitwise của Python trong hướng dẫn này

Đánh dấu là đã hoàn thành

Xem ngay Hướng dẫn này có một khóa học video liên quan do nhóm Real Python tạo. Xem nó cùng với hướng dẫn bằng văn bản để hiểu sâu hơn. Toán tử nhị phân, byte và bit trong Python

🐍 Thủ thuật Python 💌

Nhận một Thủ thuật Python ngắn và hấp dẫn được gửi đến hộp thư đến của bạn vài ngày một lần. Không có thư rác bao giờ. Hủy đăng ký bất cứ lúc nào. Được quản lý bởi nhóm Real Python

Gửi cho tôi thủ thuật Python »

Giới thiệu về Bartosz Zaczyński

Bartosz là người hướng dẫn bootcamp, tác giả và lập trình viên đa ngôn ngữ yêu thích Python. Anh ấy giúp sinh viên của mình tiếp cận công nghệ phần mềm bằng cách chia sẻ kinh nghiệm thương mại hơn một thập kỷ trong ngành CNTT

» Thông tin thêm về Bartosz

Mỗi hướng dẫn tại Real Python được tạo bởi một nhóm các nhà phát triển để nó đáp ứng các tiêu chuẩn chất lượng cao của chúng tôi. Các thành viên trong nhóm đã làm việc trong hướng dẫn này là

Aldren

David

Geir Arne

Joanna

Gia-cốp

Bậc thầy Kỹ năng Python trong thế giới thực Với quyền truy cập không giới hạn vào Python thực

Tham gia với chúng tôi và có quyền truy cập vào hàng nghìn hướng dẫn, khóa học video thực hành và cộng đồng các Pythonistas chuyên gia

Nâng cao kỹ năng Python của bạn »

Chuyên gia Kỹ năng Python trong thế giới thực
Với quyền truy cập không giới hạn vào Python thực

Tham gia với chúng tôi và có quyền truy cập vào hàng ngàn hướng dẫn, khóa học video thực hành và cộng đồng Pythonistas chuyên gia

Nâng cao kỹ năng Python của bạn »

Bạn nghĩ sao?

Đánh giá bài viết này

Tweet Chia sẻ Chia sẻ Email

Bài học số 1 hoặc điều yêu thích mà bạn đã học được là gì?

Mẹo bình luận. Những nhận xét hữu ích nhất là những nhận xét được viết với mục đích học hỏi hoặc giúp đỡ các sinh viên khác. và nhận câu trả lời cho các câu hỏi phổ biến trong cổng thông tin hỗ trợ của chúng tôi

Làm cách nào để tính toán bit trong Python?

Bước 1. số đầu vào. Bước 2. chuyển đổi số thành nhị phân của nó bằng hàm bin[]. Bước 3. xóa hai ký tự đầu tiên '0b' của chuỗi nhị phân đầu ra vì hàm bin nối thêm tiền tố 'ob' trong chuỗi đầu ra. Bước 4. sau đó tính độ dài của chuỗi nhị phân

Python ints 32 hay 64 bit?

Python sử dụng số nguyên có dấu int [ 32 bit , tôi không biết liệu chúng có phải là số nguyên C ẩn giấu hay không] cho các giá trị . e. bignums] cho mọi thứ lớn hơn.

Toán tử bit trong Python là gì?

Các toán tử theo bit của Python được được sử dụng để thực hiện các phép tính theo bit trên các số nguyên . Các số nguyên được chuyển đổi thành định dạng nhị phân và sau đó các phép toán được thực hiện từng bit một, do đó có tên là toán tử bitwise. Các toán tử bitwise của Python chỉ hoạt động trên các số nguyên và đầu ra cuối cùng được trả về ở định dạng thập phân.

Chủ Đề