Dịch chuyển phải mã 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 [_______09] 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

Toán tửVí dụÝ nghĩa

>>> 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 [độc quyền 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
>>> 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
100Bitwise left shift
>>> 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
101
>>> 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
102Bitwise shift right

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 [

>>> 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
103]

Điều này liên quan đến cách Python đại diện cho các số nguyên bên trong. 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

Toán tửVí dụTương đương với

>>> 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
105
>>> 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
106
>>> 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
107
>>> 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
108
>>> 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
109
>>> 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
110
>>> 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
111
>>> 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
112
>>> 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
113
>>> 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
114
>>> 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
115
>>> 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
116
>>> 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
117
>>> 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
118
>>> 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
119

Đâ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ó, thì hãy tiếp tục và chuyển sang phần Toán tử logic bit 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

>>> 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
120 trên mỗi ký tự

>>>

>>> 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

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ự. Ký hiệu euro [

>>> 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
121] 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

>>>

>>> 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

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

>>>

>>> 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

Đượ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

>>> 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
122,
>>> 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
123 và
>>> 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
124 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

>>>

>>> 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

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 toán tử bitwise để thực hiện logic Boolean 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 như

>>> 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
125,
>>> 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
126 và
>>> 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
127, 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

>>> 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

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

>>> 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

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ể

>>>

>>> 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

Biểu thức được tạo bởi các toán tử theo bit ước tính thành

>>> 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
128, trong khi biểu thức tương tự được tạo từ các toán tử logic ước tính thành
>>> 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
129. Đó là bởi vì các toán tử bit được ưu tiên hơn các toán tử so sánh, thay đổi cách diễn giải 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
8

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 đúng là gì, nó sẽ không ảnh hưởng đến kết quả, vì vậy không có ích gì khi 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

>>>

>>> 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
10

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

>>>

>>> 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
11

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ể

Biểu thức Giá trị nhị phân Giá trị thập phân ____113110011100215610

>>> 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
13211010025210
>>> 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 tối thiểu 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

Biểu thức Giá trị nhị phân Giá trị thập phân ____113110011100215610

>>> 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
13211010025210
>>> 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 tối đa 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 AND, OR và NOT, 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ó

>>> 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
12

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 [

>>> 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
139]

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ụ

Biểu thức Giá trị nhị phân Giá trị thập phân ____113110011100215610

>>> 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
13211010025210
>>> 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 phương 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

Biểu thức Giá trị nhị phân Giá trị thập phân ____113110011100215610

>>> 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 số nguyên không dấu, nhưng Python không hỗ trợ chúng nguyên bả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

>>>

>>> 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
13

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 biểu diễn số nhị phân khác nhau. Hiện tại, giải pháp khắc phục nhanh là tận dụng 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
14

Đó là một ví dụ hoàn hảo về bitmask mà bạn sẽ khám phá trong một trong các 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

Biểu thức Giá trị nhị phân Giá trị thập phân ____113110011123910

>>> 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
148100111027810
>>> 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
14910011100215610
>>> 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
150100111000231210

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 giới hạn độ 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 mặt nạ bit với toán tử AND 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
15

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 [

>>> 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
101] 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

Biểu thức Giá trị nhị phân Giá trị thập phân ____113110011101215710

>>> 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
153100111027810
>>> 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
15410011123910
>>> 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
1551001121910

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

>>>

>>> 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
16

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ố

>>>

>>> 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
17

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ở

>>>

>>> 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
18

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à trừ 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 [

>>> 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
103]. 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ó hình dung rõ hơn khi đến phần biểu diễn số nhị 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

>>> 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
19

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

>>> 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
50

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

>>> 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
157

>>> 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
51

Nó sử dụng 32 bit để lưu trữ các số nguyên có dấu 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

>>> 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
158 tích hợp sẵn

>>>

>>> 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
52

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 ý

>>>

>>> 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
53

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 [

>>> 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
101], đô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

In other words, it fills the gap on the left with whatever the sign bit was. Kết hợp với biểu diễn bổ sung của hai nhị phân đã ký, đ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. Instead, it follows a custom adaptive strategy that works like sign-magnitude with an unlimited number of bits. It converts numbers back and forth between their internal representation and two’s complement to mimic the standard behavior of the arithmetic shift

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

Binary Number Representations

You’ve experienced firsthand the lack of unsigned data types in Python when using the bitwise negation [

>>> 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] and the right shift 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
101]. You’ve seen hints about the unusual approach to storing integers in Python, which makes handling negative numbers tricky. To use bitwise operators effectively, you need to know about the various representations of numbers in binary

Unsigned Integers

In programming languages like C, you choose whether to use the signed or unsigned flavor of a given numeric type. Unsigned data types are more suitable when you know for sure that you’ll never need to deal with negative numbers. By allocating that one extra bit, which would otherwise serve as a sign bit, you practically double the range of available values

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

The quickest way to get a taste of the unsigned numeric types in Python is to use the previously mentioned

>>> 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
158 module

>>>

>>> 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
54

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, bit quan trọng nhất đóng vai trò là 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 [

>>> 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
163] và số 1 biểu thị số â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
164]. 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

>>>

>>> 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
55

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

Biểu thức Chuỗi nhị phân Giá trị ký-độ lớn

>>> 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
1310010101024210
>>> 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
132101010102-4210
>>> 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
167110101002-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 của các giá trị vẫn đối xứng và tiếp tục có hai cách biểu thị số không. 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. By applying a small modification, you can eliminate double zero and simplify the binary arithmetic in one go. Để 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

Positive SequenceOne’s Complement [NOT]Two’s Complement [NOT+1]000000002111111112000000002000000012111111102111111112000000102111111012111111102⋮⋮⋮011111112100000002100000012

Đ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 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0-2726252423222120-1286432168421

Notice the minus sign next to the leftmost bit weight. Deriving a decimal value from a binary sequence like that is only a matter of adding appropriate columns. For example, the value of 110101102 in 8-bit two’s complement representation is the same as the sum. -12810 + 6410 + 1610 + 410 + 210 = -4210

With the two’s complement representation, you no longer need to worry about the carryover bit unless you want to use it as an overflow detection mechanism, which is kind of neat

There are a few other variants of signed number representations, but they’re not as popular

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

Floating-Point Numbers

The IEEE 754 standard defines a binary representation for real numbers consisting of the sign, exponent, and mantissa bits. Without getting into too many technical details, you can think of it as the scientific notation for binary numbers. The decimal point “floats” around to accommodate a varying number of significant figures, except it’s a binary point

Two data types conforming to that standard are widely supported

  1. Single precision. 1 sign bit, 8 exponent bits, 23 mantissa bits
  2. Double precision. 1 sign bit, 11 exponent bits, 52 mantissa bits

Kiểu dữ liệu

>>> 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
168 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 hiển thị các số đó ở dạng nhị phân 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

>>>

>>> 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
56

Trong những trường hợp như vậy, tốt hơn hết là bạn nên sử dụng mô-đun

>>> 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
169 của Python, mô-đun này thực hiện 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 độ dài bit đã cho. Ví dụ: bạn có thể cho biết bạn muốn giữ nguyên bao nhiêu chữ số

>>>

>>> 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
57

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

>>> 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
170, đâ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ụ

LoạiKích thướcGiá trị tối thiểuGiá trị tối đa

>>> 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
1711 byte-128127
>>> 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
1722 byte-32,76832,767
>>> 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
1734 byte-2,147,483,6482,147,483,647
>>> 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
1748 byte-9,223,372,036,854,775,8089,248,703,70,3

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 suy

Trong CPython, các số nguyên rất nhỏ trong khoảng từ -510 đến 25610 được đặt 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

>>>

>>> 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
58

Cả hai biến có cùng một danh tính vì chúng đề cập đến cùng một đối tượng chính xác 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

>>>

>>> 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
59

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ì

>>>

>>> 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
90

Các biến

>>> 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
131 và
>>> 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
132 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
>>> 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
177 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

>>> 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
178. 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

>>>

>>> 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
91

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

>>> 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
179 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

>>>

>>> 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
92

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

>>>

>>> 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
93

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

>>>

>>> 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
94

Đ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 cùng têntuple trong Python 2, thay vào đó, bạn nên tham khảo thuộc tính

>>> 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
180

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

>>> 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
173 và
>>> 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
174 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

>>> 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
178, thường chiếm 32 hoặc 64 bit và cung cấp một phạm vi giá trị hạn chế

>>>

>>> 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
95

Đố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

>>>

>>> 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
96

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

>>> 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
184 ở cuối một chữ cái, có thể được sử dụng để thực thi loại đã cho bằng tay

>>>

>>> 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
97

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. [Nguồn]

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 để mô phỏng bit dấu 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
>>> 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
173 sang nhị phân

Để hiển thị các bit tạo thành 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ị

>>>

>>> 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
98

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

>>> 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
186 với số làm đối số

>>>

>>> 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
99

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ố

>>> 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
187 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

You can use such literals verbatim in your code, too

>>>

>>> 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
30

Other integer literals available in Python are the hexadecimal and octal ones, which you can obtain with the

>>> 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
188 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
189 functions, respectively

>>>

>>> 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
31

Notice how the hexadecimal system, which is base sixteen, takes advantage of letters

>>> 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
190 through
>>> 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
191 to augment the set of available digits. The octal literals in other programming languages are usually prefixed with plain zero, which might be confusing. Python explicitly forbids such literals to avoid making a mistake

>>>

>>> 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
32

You can express the same value in different ways using any of the mentioned integer literals

>>>

>>> 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
33

Choose the one that makes the most sense in context. For example, it’s customary to express bitmasks with hexadecimal notation. On the other hand, the octal literal is rarely seen these days

All numeric literals in Python are case insensitive, so you can prefix them with either lowercase or uppercase letters

>>>

>>> 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
34

This also applies to floating-point number literals that use scientific notation as well as complex number literals

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

Converting Binary to
>>> 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
173

Once you have your bit string ready, you can get its decimal representation by taking advantage of a binary literal

>>>

>>> 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
35

This is a quick way to do the conversion while working inside the interactive Python interpreter. Unfortunately, it won’t let you convert bit sequences synthesized at runtime because all literals need to be hard-coded in the source code

Note. You might be tempted to evaluate Python code with

>>> 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
193, but that’s an easy way to compromise the security of your program, so don’t do it

Calling

>>> 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
194 with two arguments will work better in the case of dynamically generated bit strings

>>>

>>> 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
36

The first argument is a string of digits, while the second one determines the base of the numeral system. Unlike a binary literal, a string can come from anywhere, even a user typing on the keyboard. For a deeper look at

>>> 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
194, you can expand the box below

Other Uses 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
194Show/Hide

There are other ways to call

>>> 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
194. For example, it returns zero when called without arguments

>>>

>>> 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
37

This feature makes it a common pattern in the

>>> 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
198 collection, which needs a default value provider. Take this as an example

>>>

>>> 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
38

Here,

>>> 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
194 helps to count words in a sentence. It’s called automatically whenever
>>> 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
198 needs to initialize the value of a missing key in the dictionary

Another popular use 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
194 is typecasting. For example, when you pass
>>> 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
194 a floating-point value, it truncates the value by removing the fractional component

>>>

>>> 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
39

When you give it a string, it tries to parse out a number from it

>>>

>>> 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
40

In general,

>>> 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
194 will accept an object of any type as long as it defines a special method that can handle the conversion

So far, so good. But what about negative numbers?

Emulating the Sign Bit

When you call

>>> 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
186 on a negative integer, it merely prepends the minus sign to the bit string obtained from the corresponding positive value

>>>

>>> 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
41

Changing the sign of a number doesn’t affect the underlying bit string in Python. Conversely, you’re allowed to prefix a bit string with the minus sign when transforming it to decimal form

>>>

>>> 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
42

That makes sense in Python because, internally, it doesn’t use the sign bit. You can think of the sign of an integer number in Python as a piece of information stored separately from the modulus

However, there are a few workarounds that let you emulate fixed-length bit sequences containing the sign bit

  • Bitmask
  • Modulo operation [
    >>> 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
    
    139]
  • >>> 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
    
    158 module
  • >>> 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
    
    507 module
  • >>> 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
    
    508 module

You know from earlier sections that to ensure a certain bit-length of a number, you can use a nifty bitmask. For example, to keep one byte, you can use a mask composed of exactly eight turned-on bits

>>>

>>> 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
43

Masking forces Python to temporarily change the number’s representation from sign-magnitude to two’s complement and then back again. If you forget about the decimal value of the resulting binary literal, which is equal to 21410, then it’ll represent -4210 in two’s complement. The leftmost bit will be the sign bit

Alternatively, you can take advantage of the modulo operation that you used previously to simulate the logical right shift in Python

>>>

>>> 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
44

If that looks too convoluted for your taste, then you can use one of the modules from the standard library that express the same intent more clearly. For example, using

>>> 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
158 will have an identical effect

>>>

>>> 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
45

You’ve seen it before, but just as a reminder, it’ll piggyback off the unsigned integer types from C

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

>>> 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
507 module. It defines a data structure that’s similar to a
>>> 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
511 but is only allowed to hold elements of the same numeric type. Khi khai báo một mảng, bạn cần chỉ định kiểu của nó ở phía trước bằng một chữ cái tương ứng

>>>

>>> 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
46

For example,

>>> 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
512 stands for an 8-bit signed byte, while
>>> 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
513 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

>>> 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
508 module, which uses a similar set of format characters for type declarations

>>>

>>> 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
47

Packing lets you lay objects in memory according to the given C data type specifiers. It returns a read-only

>>> 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
515 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

>>> 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
48

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

>>>

>>> 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
49

Python’s

>>> 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
194 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

>>> 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
507 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
508 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

>>> 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
168 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

>>>

>>> 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
50

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

>>> 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
515 of a floating-point number in Python, you can pack it using the familiar
>>> 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
508 module

>>>

>>> 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
51

Ignore the format characters passed through the first argument. They won’t make sense until you get to the byte order section below. Behind this rather obscure textual representation hides a list of eight integers

>>>

>>> 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
52

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

>>>

>>> 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
53

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

>>> 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
168 from a similar bit string, you can reverse the process

>>>

>>> 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
54

>>> 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
523 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

>>>

>>> 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
55

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

It doesn’t matter which direction computers choose to read the bytes from as long as they apply the same rules everywhere. 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. Đây được gọi là thứ tự big-endian. Conversely, when bytes are stored from right to left, the least-significant byte comes first. That’s called little-endian order

Ghi chú. These humorous names draw inspiration from the eighteenth-century novel Gulliver’s Travels by Jonathan Swift. Tác giả mô tả một cuộc xung đột giữa Little-Endians và Big-Endians về cách chính xác để phá vỡ vỏ trứng luộc. Trong khi Little-Endians thích bắt đầu với phần cuối nhỏ nhọn, thì Big-Endians thích phần cuối lớn hơn

Cách nào tốt hơn?

Từ quan điểm thực tế, không có lợi thế thực sự nào khi sử dụng cái này hơn cái kia. Có thể có một số lợi ích nhỏ về hiệu suất ở cấp độ phần cứng, nhưng bạn sẽ không nhận thấy chúng. Các giao thức mạng chính sử dụng thứ tự big-endian, cho phép chúng lọc các gói dữ liệu nhanh hơn dựa trên thiết kế phân cấp địa chỉ IP. Ngoài ra, một số người có thể thấy thuận tiện hơn khi làm việc với một thứ tự byte cụ thể khi gỡ lỗi

Dù bằng cách nào, nếu bạn không hiểu đúng và trộn lẫn hai tiêu chuẩn, thì điều tồi tệ bắt đầu xảy ra

>>>

>>> 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
56

Khi bạn tuần tự hóa một số giá trị thành một luồng byte bằng một quy ước và thử đọc lại giá trị đó bằng một quy ước khác, bạn sẽ nhận được một kết quả hoàn toàn vô dụng. Trường hợp này rất có thể xảy ra khi dữ liệu được gửi qua mạng, nhưng bạn cũng có thể gặp trường hợp này khi đọc tệp cục bộ ở định dạng cụ thể. Ví dụ: tiêu đề của bitmap Windows luôn sử dụng little-endian, trong khi JPEG có thể sử dụng cả hai thứ tự byte

Bản địa Endianness

Để tìm hiểu độ bền của nền tảng của bạn, bạn có thể sử dụng mô-đun

>>> 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
524

>>>

>>> 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
57

Tuy nhiên, bạn không thể thay đổi tuổi thọ vì đó là một tính năng nội tại của kiến ​​trúc CPU của bạn. Không thể chế nhạo nó cho mục đích thử nghiệm mà không có ảo hóa phần cứng như QEMU, vì vậy ngay cả VirtualBox phổ biến cũng không giúp được gì

Đáng chú ý, dòng vi xử lý x86 của Intel và AMD, cung cấp năng lượng cho hầu hết các máy tính xách tay và máy tính để bàn hiện đại, đều là những bộ vi xử lý nhỏ. Các thiết bị di động dựa trên kiến ​​trúc ARM năng lượng thấp, là kiến ​​trúc hai chiều, trong khi một số kiến ​​trúc cũ hơn như Motorola 68000 cổ đại chỉ là kiểu cuối lớn

Để biết thông tin về cách xác định tuổi thọ trong C, hãy mở rộng hộp bên dưới

Kiểm tra thứ tự byte trong CHiển thị/Ẩn

Trước đây, cách để có được độ bền của máy trong C là khai báo một số nguyên nhỏ và sau đó đọc byte đầu tiên của nó bằng một con trỏ

>>> 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
58

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

Khi bạn biết độ bền gốc của máy, bạn sẽ muốn chuyển đổi giữa các thứ tự byte khác nhau khi thao tác dữ liệu nhị phân. Một cách phổ biến để làm như vậy, bất kể loại dữ liệu hiện có, là đảo ngược một đối tượng chung chung

>>> 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
515 hoặc một chuỗi các số nguyên đại diện cho các byte đó

>>>

>>> 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
59

Tuy nhiên, thường thuận tiện hơn khi sử dụng mô-đun

>>> 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
508, mô-đun này cho phép bạn xác định các kiểu dữ liệu C tiêu chuẩn. Ngoài ra, nó cho phép bạn yêu cầu một thứ tự byte nhất định với một công cụ sửa đổi tùy chọn

>>>

>>> 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
80

Dấu lớn hơn [

>>> 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
527] chỉ ra rằng các byte được sắp xếp theo thứ tự big-endian, trong khi ký hiệu nhỏ hơn [
>>> 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
528] tương ứng với little-endian. Nếu bạn không chỉ định một, thì tuổi thọ gốc được giả định. Có một vài công cụ sửa đổi khác, chẳng hạn như dấu chấm than [
>>> 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
529], biểu thị thứ tự byte mạng

Thứ tự byte mạng

Mạng máy tính được tạo thành từ các thiết bị không đồng nhất như máy tính xách tay, máy tính để bàn, máy tính bảng, điện thoại thông minh và thậm chí cả bóng đèn được trang bị bộ điều hợp Wi-Fi. Tất cả chúng đều cần các giao thức và tiêu chuẩn đã được thống nhất, bao gồm cả thứ tự byte để truyền nhị phân, để giao tiếp hiệu quả

Vào buổi bình minh của Internet, người ta đã quyết định rằng thứ tự byte cho các giao thức mạng đó sẽ là big-endian

Các chương trình muốn giao tiếp qua mạng có thể lấy API C cổ điển, loại bỏ các chi tiết khó hiểu bằng một lớp ổ cắm. Python kết thúc API đó thông qua mô-đun

>>> 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
530 tích hợp. Tuy nhiên, trừ khi bạn đang viết một giao thức nhị phân tùy chỉnh, có thể bạn sẽ muốn tận dụng lợi thế của sự trừu tượng hóa ở mức cao hơn, chẳng hạn như giao thức HTTP, dựa trên văn bản

Trường hợp mô-đun

>>> 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
530 có thể hữu ích trong chuyển đổi thứ tự byte. Nó hiển thị một số chức năng từ API C, với các tên xoắn lưỡi, đặc biệt của chúng

>>>

>>> 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
81

Nếu máy chủ của bạn đã sử dụng thứ tự byte cuối lớn, thì không cần phải làm gì nữa. Các giá trị sẽ được giữ nguyên

Bitmasks

Một bitmask hoạt động giống như một khuôn tô graffiti ngăn không cho sơn phun lên các khu vực cụ thể của bề mặt. Nó cho phép bạn cô lập các bit để áp dụng một số chức năng trên chúng một cách có chọn lọc. Bitmasking liên quan đến cả toán tử logic theo bit và toán tử dịch chuyển theo bit mà bạn đã đọc về

Bạn có thể tìm thấy bitmask trong nhiều ngữ cảnh khác nhau. Ví dụ: mặt nạ mạng con trong địa chỉ IP thực chất là một bitmask giúp bạn trích xuất địa chỉ mạng. Các kênh pixel, tương ứng với các màu đỏ, lục và lam trong mô hình RGB, có thể được truy cập bằng bitmask. Bạn cũng có thể sử dụng một bitmask để xác định các cờ Boolean mà sau đó bạn có thể đóng gói trên một trường bit

Có một số loại hoạt động phổ biến liên quan đến bitmasks. Bạn sẽ có một cái nhìn nhanh về một số trong số họ dưới đây

nhận được một chút

Để đọc giá trị của một bit cụ thể trên một vị trí nhất định, bạn có thể sử dụng bit AND đối với mặt nạ bit chỉ bao gồm một bit tại chỉ mục mong muốn

>>>

>>> 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
82

Mặt nạ sẽ chặn tất cả các bit ngoại trừ bit mà bạn quan tâm. Nó sẽ dẫn đến kết quả bằng 0 hoặc lũy thừa hai với số mũ bằng chỉ số bit. Thay vào đó, nếu bạn muốn nhận được câu trả lời có hoặc không đơn giản, thì bạn có thể chuyển sang bên phải và kiểm tra bit ít quan trọng nhất

>>>

>>> 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
83

Lần này, nó sẽ chuẩn hóa giá trị bit để nó không bao giờ vượt quá một. Sau đó, bạn có thể sử dụng hàm đó để lấy giá trị Boolean

>>> 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
128 hoặ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
129 thay vì giá trị số

Đặt một Bit

Đặt một chút cũng tương tự như lấy một. Bạn tận dụng cùng một bitmask như trước, nhưng thay vì sử dụng AND theo bit, bạn sử dụng 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
84

Mặt nạ giữ lại tất cả các bit ban đầu trong khi thực thi một bit nhị phân tại chỉ mục đã chỉ định. Nếu bit đó đã được đặt, giá trị của nó sẽ không thay đổi

Gỡ cài đặt một Bit

Để xóa một chút, bạn muốn sao chép tất cả các chữ số nhị phân trong khi thực thi số 0 tại một chỉ mục cụ thể. Bạn có thể đạt được hiệu ứng này bằng cách sử dụng lại cùng một bitmask, nhưng ở dạng đảo ngượ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
85

Sử dụng bitwise NOT trên một số dương luôn tạo ra một giá trị âm trong Python. Mặc dù điều này nói chung là không mong muốn, nhưng nó không thành vấn đề ở đây vì bạn ngay lập tức áp dụng toán tử AND theo chiều bit. Đến lượt nó, điều này kích hoạt chuyển đổi của mặt nạ thành biểu diễn bổ sung của hai, mang lại cho bạn kết quả như mong đợi

Chuyển đổi một chút

Đôi khi, thật hữu ích khi có thể bật và tắt một chút định kỳ. Đó 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ế

>>>

>>> 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
86

Lưu ý cùng một bitmask được sử dụng lại. Một nhị phân trên vị trí đã chỉ định sẽ làm cho bit tại chỉ mục đó đảo ngược giá trị của nó. Việc có các số 0 nhị phân ở những vị trí còn lại sẽ đảm bảo rằng phần còn lại của các bit sẽ được sao chép

Quá tải toán tử Bitwise

Miền chính của toán tử bitwise là số nguyên. Đó là nơi chúng có ý nghĩa nhất. Tuy nhiên, bạn cũng đã thấy chúng được sử dụng trong ngữ cảnh Boolean, trong đó chúng thay thế các toán tử logic. Python cung cấp các triển khai thay thế cho một số toán tử của nó và cho phép bạn nạp chồng chúng cho các kiểu dữ liệu mới

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

  • >>> 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
    
    173
  • >>> 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
    
    535
  • >>> 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
    
    536 và
    >>> 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
    
    537
  • >>> 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
    
    538 [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

>>> 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
131 và
>>> 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
132 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

>>> 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
541
>>> 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
>>> 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
543
>>> 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
109
>>> 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
545
>>> 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
>>> 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
547
>>> 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
106
>>> 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
549
>>> 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
>>> 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
551
>>> 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
112

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 [

>>> 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
164], 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

>>>

>>> 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
87

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à

>>> 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
554 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

>>>

>>> 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
88

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

>>> 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
554 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

>>> 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
89

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

>>> 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
538 chỉ hỗ trợ bitwise OR, 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

>>> 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
557

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

gấu trúc 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

>>> 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
558 và
>>> 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
559 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ẻ

Mọi thứ trở nên thú vị hơn với các thư viện cung cấp cho các toán tử bitwise những ý nghĩa hoàn toàn mới. Ví dụ, SQLAlchemy cung cấp một cú pháp nhỏ gọn để truy vấn cơ sở dữ liệu

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

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] cuối cùng sẽ dịch thành một đoạn truy vấn SQL. Tuy nhiên, điều đó không rõ ràng lắm, ít nhất là không phải với IDE của tôi, nó phàn nàn về việc sử dụng các toán tử bitwise không phức tạp khi nó nhìn thấy chúng trong loại biểu thức này. Nó ngay lập tức gợi ý thay thế mọi lần xuất hiện của
>>> 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 bằng một
>>> 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
125 hợp lý, mà không biết rằng làm như vậy sẽ khiến mã ngừng hoạt động

Kiểu nạp chồng toán tử này là một phương pháp gây tranh cãi dựa trên phép thuật tiềm ẩn mà bạn phải biết trước. Một số ngôn ngữ lập trình như Java ngăn chặn sự lạm dụng đó bằng cách không cho phép quá tải toán tử hoàn toàn. Python tự do hơn về vấn đề đó và tin tưởng rằng bạn biết mình đang làm gì

Loại dữ liệu tùy chỉnh

Để tùy chỉnh hành vi của các toán tử bitwise của Python, bạn phải định nghĩa một lớp và sau đó triển khai các phương thức ma thuật tương ứng trong đó. Đồng thời, bạn không thể xác định lại hành vi của toán tử bitwise cho các loại hiện có. Quá tải toán tử chỉ có thể thực hiện được trên các kiểu dữ liệu mới

Dưới đây là tóm tắt nhanh về các phương thức đặc biệt cho phép bạn quá tải các toán tử theo bit

Magic MethodExpression

>>> 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
563
>>> 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
564
>>> 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
565
>>> 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
566
>>> 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
567
>>> 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
568
>>> 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
569
>>> 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
570
>>> 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
571
>>> 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
572
>>> 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
573
>>> 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
574
>>> 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
575
>>> 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
576
>>> 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
577
>>> 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
578
>>> 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
579
>>> 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
580
>>> 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
581
>>> 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
582
>>> 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
583
>>> 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
584
>>> 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
585
>>> 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
586
>>> 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
587
>>> 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
588
>>> 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
589
>>> 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
590
>>> 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
591
>>> 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
592
>>> 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
593
>>> 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
594

Bạn không cần phải xác định tất cả chúng. Ví dụ: để có một cú pháp thuận tiện hơn một chút để nối thêm và thêm các phần tử vào deque, chỉ cần triển khai

>>> 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
595 và
>>> 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
596 là đủ

>>>

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

Lớp do người dùng định nghĩa này bao bọc một deque để tái sử dụng triển khai của nó và bổ sung cho nó hai phương thức bổ sung cho phép thêm các mục vào đầu bên trái hoặc bên phải của bộ sưu tập

Steganography bit ít quan trọng nhất

Whew, đó là rất nhiều để xử lý. Nếu bạn vẫn đang vò đầu bứt tai, tự hỏi tại sao bạn lại muốn sử dụng toán tử bitwise, thì đừng lo lắng. Đã đến lúc thể hiện những gì bạn có thể làm với chúng một cách thú vị

Để làm theo các ví dụ trong phần này, bạn có thể tải xuống mã nguồn bằng cách 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

Bạn sẽ tìm hiểu về steganography và áp dụng khái niệm này để nhúng bí mật các tập tin tùy ý vào các hình ảnh bitmap

Mật mã vs Steganography

Mật mã là về việc thay đổi một tin nhắn thành một tin nhắn chỉ có thể đọc được đối với những người có khóa phù hợp. Mọi người khác vẫn có thể xem tin nhắn được mã hóa, nhưng nó sẽ không có ý nghĩa gì với họ. Một trong những dạng mật mã đầu tiên là mật mã thay thế, chẳng hạn như mật mã Caesar được đặt theo tên của Julius Caesar

Steganography tương tự như mật mã vì nó cũng cho phép bạn chia sẻ các tin nhắn bí mật với đối tượng mong muốn của mình. Tuy nhiên, thay vì sử dụng mã hóa, nó khéo léo giấu thông tin trong một phương tiện không thu hút sự chú ý. Các ví dụ bao gồm sử dụng mực vô hình hoặc viết một chữ cái đầu trong đó chữ cái đầu tiên của mỗi từ hoặc dòng tạo thành một thông điệp bí mật

Trừ khi bạn biết rằng một tin nhắn bí mật đã bị che giấu và phương pháp khôi phục nó, nếu không bạn có thể bỏ qua nhà cung cấp dịch vụ. Bạn có thể kết hợp cả hai kỹ thuật để an toàn hơn, ẩn tin nhắn được mã hóa thay vì tin nhắn gốc

Có rất nhiều cách để buôn lậu dữ liệu bí mật trong thế giới kỹ thuật số. Đặc biệt, các định dạng tệp chứa nhiều dữ liệu, chẳng hạn như tệp âm thanh, video hoặc hình ảnh, rất phù hợp vì chúng cho bạn nhiều không gian để làm việc. 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ỉ

Bên dưới, bạn sẽ đưa dữ liệu bí mật vào một bitmap đơn giản, dễ đọc và ghi bằng Python mà không cần phụ thuộc bên ngoài

Định dạng tệp bitmap

Từ bitmap thường đề cập đến định dạng tệp bitmap Windows [______3597], hỗ trợ một số cách biểu diễn pixel thay thế. Để làm cho cuộc sống dễ dàng hơn, bạn sẽ giả sử rằng các pixel được lưu trữ ở định dạng RGB [đỏ, lục và lam] 24 bit không nén. Một pixel sẽ có ba kênh màu, mỗi kênh có thể chứa các giá trị từ 010 đến 25510

Mỗi bitmap bắt đầu bằng một tiêu đề tệp, chứa siêu dữ liệu như chiều rộng và chiều cao của hình ảnh. Dưới đây là một vài trường thú vị và vị trí của chúng so với phần đầu của tiêu đề

FieldByte OffsetBytes LengthTypeSample ValueSignature

>>> 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
5982StringBMFile Size
>>> 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
5994Unsigned int7,629,186Reserved #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
9002Bytes0Reserved #2
>>> 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
9012Bytes0Pixels Offset
>>> 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
9024Unsigned int122Pixels Size
>>> 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
9034Unsigned int7,629,064Image Width
>>> 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
9044Unsigned int1,954Image Height
>>> 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
9054Unsigned int1,301Bits Per Pixel
>>> 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
9062Unsigned short24Compression
>>> 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
9074Unsigned int0Colors Palette
>>> 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
9084Unsigned int0

Bạn có thể suy ra từ tiêu đề này rằng bitmap tương ứng rộng 1.954 pixel và cao 1.301 pixel. Nó không sử dụng nén, cũng không có bảng màu. Mỗi pixel chiếm 24 bit hoặc 3 byte và dữ liệu pixel thô bắt đầu ở offset 12210

Bạn có thể mở bitmap ở chế độ nhị phân, tìm kiếm phần bù mong muốn, đọc số byte đã cho và giải tuần tự hóa chúng bằng cách sử dụng

>>> 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
508 như trước đây

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

Lưu ý rằng tất cả các trường số nguyên trong ảnh bitmap được lưu trữ theo thứ tự byte cuối nhỏ

Bạn có thể đã nhận thấy một sự khác biệt nhỏ giữa số byte pixel được khai báo trong tiêu đề và số byte do kích thước hình ảnh. Khi bạn nhân 1.954 pixel × 1.301 pixel × 3 byte, bạn sẽ nhận được giá trị nhỏ hơn 2.602 byte so với 7.629.064

Điều này là do byte pixel được đệm bằng số 0 để mỗi hàng là bội số của bốn byte. 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

Ghi chú. Để 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 đến 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

>>> 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
515 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

>>> 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
173 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

>>> 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
912 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 xóa thư mục mẹ dư thừa khỏi đường dẫn bằng cách sử dụng

>>> 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
913 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à

>>> 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
914, 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 »

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 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. Nhận các mẹo để đặt câu hỏi hay 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

>> và < trong Python là gì?

> bằng Python?
Trong Python >> được gọi là toán tử dịch phải . Nó là một toán tử bitwise. Nó yêu cầu một đại diện bitwise của đối tượng như toán hạng đầu tiên. Các bit được dịch sang phải theo số bit được quy định bởi toán hạng thứ hai.

Việc sử dụng >> là gì?

Toán tử >> là toán tử dịch phải có dấu và >>> là toán tử dịch phải không dấu. Giá trị toán hạng bên trái được di chuyển sang phải theo số bit được chỉ định bởi toán hạng bên phải.

Chức năng của shift phải là gì?

Toán tử dịch phải [ >> ] trả về số có dấu được biểu thị bằng kết quả thực hiện phép dịch mở rộng có dấu của biểu diễn nhị phân của toán hạng đầu tiên [được đánh giá là chuỗi bit bù hai] sang phải theo số bit, modulo

Chủ Đề