Hoạt động và sử dụng bitwise


102

Hãy xem xét mã này:

x = 1        # 0001
x << 2       # Shift left 2 bits: 0100
# Result: 4

x | 2        # Bitwise OR: 0011
# Result: 3

x & 1        # Bitwise AND: 0001
# Result: 1

Tôi có thể hiểu các toán tử số học trong Python (và các ngôn ngữ khác), nhưng tôi chưa bao giờ hiểu rõ về các toán tử 'bitwise'. Trong ví dụ trên (từ một cuốn sách Python), tôi hiểu về sự dịch chuyển bên trái nhưng không hiểu được sự dịch chuyển bên trái.

Ngoài ra, các toán tử bitwise thực sự được sử dụng để làm gì? Tôi đánh giá cao một số ví dụ.


11
Điều này có thể được quan tâm: stackoverflow.com/questions/276706/what-are-bitwise-operators
outis

Câu trả lời:


163

Toán tử bitwise là toán tử hoạt động trên các giá trị nhiều bit, nhưng về mặt khái niệm từng bit một.

  • ANDlà 1 chỉ khi cả hai đầu vào của nó là 1, nếu không thì là 0.
  • ORlà 1 nếu một hoặc cả hai đầu vào của nó là 1, ngược lại là 0.
  • XORlà 1 chỉ khi chính xác một trong các đầu vào của nó là 1, ngược lại là 0.
  • NOT là 1 chỉ khi đầu vào của nó là 0, ngược lại là 0.

Chúng thường có thể được hiển thị tốt nhất dưới dạng bảng sự thật. Các khả năng đầu vào nằm trên cùng và bên trái, bit kết quả là một trong bốn giá trị (hai trong trường hợp KHÔNG vì nó chỉ có một đầu vào) được hiển thị tại giao điểm của các đầu vào.

AND | 0 1     OR | 0 1     XOR | 0 1    NOT | 0 1
----+-----    ---+----     ----+----    ----+----
 0  | 0 0      0 | 0 1       0 | 0 1        | 1 0
 1  | 0 1      1 | 1 1       1 | 1 0

Một ví dụ là nếu bạn chỉ muốn 4 bit thấp hơn của một số nguyên, bạn VÀ nó với 15 (nhị phân 1111) như vậy:

    201: 1100 1001
AND  15: 0000 1111
------------------
 IS   9  0000 1001

Các bit không trong 15 trong trường hợp đó hoạt động hiệu quả như một bộ lọc, buộc các bit trong kết quả cũng bằng 0.

Ngoài ra, >><<thường được bao gồm dưới dạng toán tử theo chiều bit, và chúng "dịch chuyển" một giá trị tương ứng sang phải và sang trái theo một số bit nhất định, loại bỏ các bit cuộn cuối mà bạn đang chuyển hướng tới và cung cấp các bit bằng 0 tại đầu kia.

Ví dụ:

1001 0101 >> 2 gives 0010 0101
1111 1111 << 4 gives 1111 0000

Lưu ý rằng sự dịch chuyển sang trái trong Python là không bình thường ở chỗ nó không sử dụng chiều rộng cố định nơi các bit bị loại bỏ - trong khi nhiều ngôn ngữ sử dụng chiều rộng cố định dựa trên kiểu dữ liệu, Python chỉ mở rộng chiều rộng để phục vụ cho các bit bổ sung. Để có được hành vi loại bỏ trong Python, bạn có thể thực hiện theo cách dịch chuyển sang trái theo chiều dọc bit, andchẳng hạn như trong giá trị 8 bit dịch chuyển sang trái bốn bit:

bits8 = (bits8 << 4) & 255

Với ý nghĩ đó, một ví dụ về toán tử Bitwise là nếu bạn có hai giá trị 4-bit mà bạn muốn đóng gói thành một một 8-bit, bạn có thể sử dụng tất cả ba nhà khai thác của bạn ( left-shift, andor):

packed_val = ((val1 & 15) << 4) | (val2 & 15)
  • Các & 15hoạt động sẽ đảm bảo rằng cả hai giá trị chỉ có giá thấp hơn 4 bit.
  • Đây << 4là dịch chuyển 4 bit sang trái để chuyển val1sang 4 bit trên cùng của giá trị 8 bit.
  • Đơn |giản là kết hợp cả hai với nhau.

Nếu val1là 7 và val2là 4:

                val1            val2
                ====            ====
 & 15 (and)   xxxx-0111       xxxx-0100  & 15
 << 4 (left)  0111-0000           |
                  |               |
                  +-------+-------+
                          |
| (or)                0111-0100

43

Một cách sử dụng điển hình:

| được sử dụng để đặt một bit nhất định thành 1

& được sử dụng để kiểm tra hoặc xóa một số bit nhất định

  • Đặt một bit (trong đó n là số bit và 0 là bit có ý nghĩa nhỏ nhất):

    unsigned char a |= (1 << n);

  • Xóa một chút:

    unsigned char b &= ~(1 << n);

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

    unsigned char c ^= (1 << n);

  • Kiểm tra một chút:

    unsigned char e = d & (1 << n);

Lấy ví dụ về trường hợp danh sách của bạn:

x | 2được sử dụng để đặt bit 1 trong số x1

x & 1được sử dụng để kiểm tra xem bit 0 của xlà 1 hay 0


38

toán tử bitwise thực sự được sử dụng để làm gì? Tôi đánh giá cao một số ví dụ.

Một trong những cách sử dụng phổ biến nhất của phép toán bit là để phân tích cú pháp các màu thập lục phân.

Ví dụ: đây là một hàm Python chấp nhận một Chuỗi giống như #FF09BEvà trả về một bộ giá trị Đỏ, Xanh lục và Xanh lam của nó.

def hexToRgb(value):
    # Convert string to hexadecimal number (base 16)
    num = (int(value.lstrip("#"), 16))

    # Shift 16 bits to the right, and then binary AND to obtain 8 bits representing red
    r = ((num >> 16) & 0xFF)

    # Shift 8 bits to the right, and then binary AND to obtain 8 bits representing green
    g = ((num >> 8) & 0xFF)

    # Simply binary AND to obtain 8 bits representing blue
    b = (num & 0xFF)
    return (r, g, b)

Tôi biết rằng có nhiều cách hiệu quả hơn để giải quyết vấn đề này, nhưng tôi tin rằng đây là một ví dụ thực sự ngắn gọn minh họa cho cả các phép toán boolean shift và bitwise.


14

Tôi nghĩ rằng phần thứ hai của câu hỏi:

Ngoài ra, các toán tử bitwise thực sự được sử dụng để làm gì? Tôi đánh giá cao một số ví dụ.

Chỉ được giải quyết một phần. Đây là hai xu của tôi về vấn đề đó.

Các phép toán bit trong ngôn ngữ lập trình đóng vai trò cơ bản khi xử lý nhiều ứng dụng. Hầu hết tất cả các tính toán cấp thấp phải được thực hiện bằng cách sử dụng loại hoạt động này.

Trong tất cả các ứng dụng cần gửi dữ liệu giữa hai nút, chẳng hạn như:

  • mạng máy tính;

  • ứng dụng viễn thông (điện thoại di động, thông tin liên lạc vệ tinh, v.v.).

Trong lớp giao tiếp cấp thấp hơn, dữ liệu thường được gửi trong cái được gọi là khung . Khung chỉ là các chuỗi byte được gửi qua một kênh vật lý. Các khung này thường chứa dữ liệu thực tế cộng với một số trường khác (được mã hóa theo byte) là một phần của cái được gọi là tiêu đề . Tiêu đề thường chứa các byte mã hóa một số thông tin liên quan đến trạng thái của giao tiếp (ví dụ: với cờ (bit)), bộ đếm khung, mã sửa và phát hiện lỗi, v.v. Để lấy dữ liệu được truyền trong khung và xây dựng khung để gửi dữ liệu, bạn sẽ cần các thao tác bitwise chắc chắn.

Nói chung, khi xử lý loại ứng dụng đó, một API có sẵn để bạn không phải xử lý tất cả các chi tiết đó. Ví dụ: tất cả các ngôn ngữ lập trình hiện đại đều cung cấp thư viện cho các kết nối socket, vì vậy bạn không thực sự cần phải xây dựng các khung giao tiếp TCP / IP. Nhưng hãy nghĩ về những người giỏi đã lập trình các API đó cho bạn, họ chắc chắn phải xử lý việc xây dựng khung; sử dụng tất cả các loại thao tác bitwise để đi qua lại từ giao tiếp cấp thấp đến cấp cao hơn.

Như một ví dụ cụ thể, hãy tưởng tượng một số người cung cấp cho bạn một tệp chứa dữ liệu thô được phần cứng viễn thông ghi lại trực tiếp. Trong trường hợp này, để tìm các khung, bạn sẽ cần đọc các byte thô trong tệp và cố gắng tìm một số loại từ đồng bộ hóa, bằng cách quét dữ liệu từng bit. Sau khi xác định các từ đồng bộ hóa, bạn sẽ cần lấy các khung thực tế và CHIA SẺ chúng nếu cần (và đó mới chỉ là phần bắt đầu của câu chuyện) để lấy dữ liệu thực tế đang được truyền đi.

Một họ ứng dụng cấp thấp khác rất khác là khi bạn cần điều khiển phần cứng bằng một số cổng (loại cổ), chẳng hạn như cổng song song và nối tiếp. Các cổng này được điều khiển bằng cách đặt một số byte và mỗi bit trong số byte đó có một ý nghĩa cụ thể, về mặt hướng dẫn, cho cổng đó (ví dụ: xem http://en.wikipedia.org/wiki/Parallel_port ). Nếu bạn muốn xây dựng phần mềm thực hiện điều gì đó với phần cứng đó, bạn sẽ cần các thao tác bitwise để dịch các hướng dẫn bạn muốn thực hiện sang các byte mà cổng hiểu được.

Ví dụ: nếu bạn có một số nút vật lý được kết nối với cổng song song để điều khiển một số thiết bị khác, đây là dòng mã mà bạn có thể tìm thấy trong ứng dụng mềm:

read = ((read ^ 0x80) >> 4) & 0x0f; 

Hy vọng điều này đóng góp.


Tôi sẽ thêm en.wikipedia.org/wiki/Bit_banging làm một đại lộ khác để khám phá, đặc biệt nếu đọc về các cổng song song và nối tiếp như một ví dụ trong đó các phép toán bit có thể hữu ích.
Dan

6

Tôi hy vọng điều này làm rõ hai điều đó:

x | 2

0001 //x
0010 //2

0011 //result = 3

x & 1

0001 //x
0001 //1

0001 //result = 1

4
Rất tiếc ... đã cố gắng để có súng nhanh nhất ở phương Tây .... đã kết thúc như một thằng ngốc người thậm chí không biết nhị phân cho hai :( Cố định nó.
Amarghosh

1
x & 1không minh họa hiệu ứng cũng như x & 2sẽ.
dansalmo

5

Hãy coi 0 là sai và 1 là đúng. Sau đó, bitwise và (&) và hoặc (|) hoạt động giống như thông thường và và hoặc ngoại trừ chúng thực hiện tất cả các bit trong giá trị cùng một lúc. Thông thường, bạn sẽ thấy chúng được sử dụng cho cờ nếu bạn có 30 tùy chọn có thể được đặt (ví dụ như kiểu vẽ trên cửa sổ) mà bạn không muốn phải chuyển 30 giá trị boolean riêng biệt để đặt hoặc hủy đặt từng giá trị nên bạn sử dụng | để kết hợp các tùy chọn thành một giá trị và sau đó bạn sử dụng & để kiểm tra xem từng tùy chọn đã được đặt chưa. Phong cách chuyền cờ này được OpenGL sử dụng rất nhiều. Vì mỗi bit là một cờ riêng biệt nên bạn nhận được các giá trị cờ trên lũy thừa của hai (hay còn gọi là các số chỉ có một bộ bit) 1 (2 ^ 0) 2 (2 ^ 1) 4 (2 ^ 2) 8 (2 ^ 3) the lũy thừa của hai cho bạn biết bit nào được đặt nếu cờ đang bật.

Cũng lưu ý 2 = 10 vì vậy x | 2 là 110 (6) không phải 111 (7) Nếu không có bit nào trùng nhau (đúng trong trường hợp này) | hoạt động giống như phép cộng.


5

Tôi không thấy nó được đề cập ở trên nhưng bạn cũng sẽ thấy một số người sử dụng dịch chuyển trái và phải cho các phép toán số học. Dịch sang trái cho x tương đương với nhân với 2 ^ x (miễn là nó không bị tràn) và dịch sang phải tương đương với phép chia cho 2 ^ x.

Gần đây tôi đã thấy mọi người sử dụng x << 1 và x >> 1 để nhân đôi và giảm một nửa, mặc dù tôi không chắc liệu họ chỉ đang cố tỏ ra thông minh hay thực sự có lợi thế khác biệt so với các toán tử bình thường.


1
Tôi không biết về python, nhưng trong các ngôn ngữ cấp thấp hơn như C hoặc thậm chí thấp hơn - assembly, chuyển đổi theo chiều bit sẽ hiệu quả hơn nhiều. Để thấy sự khác biệt, bạn có thể viết một chương trình bằng C làm điều này theo từng cách và chỉ cần biên dịch sang mã assembly (hoặc nếu bạn biết assembly lang, bạn sẽ biết điều này :)). Xem sự khác biệt về số lượng hướng dẫn.
0xc0de

2
Lập luận của tôi chống lại việc sử dụng các toán tử dịch chuyển bit là hầu hết các trình biên dịch hiện đại có thể đã tối ưu hóa các phép toán số học nên sự thông minh là tốt nhất để tranh luận hoặc tệ nhất là chống lại trình biên dịch. Tôi không có chuyên môn về C, trình biên dịch hoặc thiết kế CPU và vì vậy không cho rằng tôi đúng. :)
P. Stallworth

Điều này sẽ cao hơn. Tôi đã phải đối phó với một số mã đang sử dụng toán tử bitwise chính xác theo cách đó, và câu trả lời đó đã giúp tôi tìm ra mọi thứ.
Philippe Oger

4

Bộ

Các tập hợp có thể được kết hợp bằng các phép toán.

  • Toán tử union |kết hợp hai tập hợp để tạo thành một tập hợp mới chứa các mục trong cả hai.
  • Người điều hành giao lộ &chỉ nhận được các mục trong cả hai.
  • Toán tử khác biệt -nhận được các mục trong tập hợp đầu tiên nhưng không nhận được các mục trong tập hợp thứ hai.
  • Toán tử chênh lệch đối xứng ^nhận các mục trong một trong hai tập hợp, nhưng không phải cả hai.

Hãy tự mình thử:

first = {1, 2, 3, 4, 5, 6}
second = {4, 5, 6, 7, 8, 9}

print(first | second)

print(first & second)

print(first - second)

print(second - first)

print(first ^ second)

Kết quả:

{1, 2, 3, 4, 5, 6, 7, 8, 9}

{4, 5, 6}

{1, 2, 3}

{8, 9, 7}

{1, 2, 3, 7, 8, 9}

Câu trả lời này hoàn toàn không liên quan đến câu hỏi, và dường như đã được sao chép và dán từ nơi khác.
doctaphred

Câu hỏi đặt ra "Các toán tử bitwise thực sự được sử dụng để làm gì?". Câu trả lời này cung cấp cách sử dụng ít được biết đến nhưng rất hữu ích của các toán tử bitwise.
Taegyung

3

Ví dụ này sẽ cho bạn thấy các hoạt động cho tất cả bốn giá trị 2 bit:

10 | 12

1010 #decimal 10
1100 #decimal 12

1110 #result = 14

10 & 12

1010 #decimal 10
1100 #decimal 12

1000 #result = 8

Đây là một ví dụ về cách sử dụng:

x = raw_input('Enter a number:')
print 'x is %s.' % ('even', 'odd')[x&1]

2

Một trường hợp sử dụng phổ biến khác là thao tác / kiểm tra quyền đối với tệp. Xem mô-đun trạng thái Python: http://docs.python.org/library/stat.html .

Ví dụ: để so sánh quyền của tệp với tập hợp quyền mong muốn, bạn có thể làm như sau:

import os
import stat

#Get the actual mode of a file
mode = os.stat('file.txt').st_mode

#File should be a regular file, readable and writable by its owner
#Each permission value has a single 'on' bit.  Use bitwise or to combine 
#them.
desired_mode = stat.S_IFREG|stat.S_IRUSR|stat.S_IWUSR

#check for exact match:
mode == desired_mode
#check for at least one bit matching:
bool(mode & desired_mode)
#check for at least one bit 'on' in one, and not in the other:
bool(mode ^ desired_mode)
#check that all bits from desired_mode are set in mode, but I don't care about 
# other bits.
not bool((mode^desired_mode)&desired_mode)

Tôi ép các kết quả dưới dạng boolean, bởi vì tôi chỉ quan tâm đến sự thật hay sai, nhưng sẽ là một bài tập đáng giá để in ra các giá trị bin () cho mỗi giá trị.


1
Bạn đã sai trong ví dụ cuối cùng. Ở đây nó như thế nào vẻ nên thích: not bool((mode ^ desired_mode) & 0777). Hoặc (dễ hiểu): not (mode & 0777) ^ desired_mode == 0. AND sẽ chỉ để lại các bit thú vị, XOR sẽ kiểm tra xem tất cả các bit mong muốn đã được thiết lập hay chưa. == 0So sánh rõ ràng có ý nghĩa hơn bool().
Vadim Fint

Tôi không nghĩ rằng điều này là cụ thể cho các hoạt động tệp. Ví dụ, trong PyQt, bạn làm điều gì đó tương tự setWindowFlags. Ví dụ: setWindowFlags(SplashScreen | WindowStaysOnTopHint). Tôi vẫn thấy điều này khó hiểu, vì nó có vẻ như một công tắc bạn đang đặt thành 'bật' nên nó có vẻ trực quan hơn đối với 'và' trong trường hợp như vậy.
eric

2

Các biểu diễn bit của số nguyên thường được sử dụng trong máy tính khoa học để biểu diễn các mảng thông tin đúng-sai bởi vì thao tác theo bit nhanh hơn nhiều so với việc lặp qua một mảng boolean. (Các ngôn ngữ cấp cao hơn có thể sử dụng ý tưởng về mảng bit.)

Một ví dụ hay và khá đơn giản về điều này là giải pháp chung cho trò chơi Nim. Hãy xem mã Python trên trang Wikipedia . Nó sử dụng nhiều bitwise độc ​​quyền hoặc ^,.


1

Có thể có một cách tốt hơn để tìm vị trí của một phần tử mảng giữa hai giá trị, nhưng như ví dụ này cho thấy, & hoạt động ở đây, ngược lại không.

import numpy as np
a=np.array([1.2, 2.3, 3.4])
np.where((a>2) and (a<3))      
#Result: Value Error
np.where((a>2) & (a<3))
#Result: (array([1]),)

1

tôi không thấy nó được đề cập, Ví dụ này sẽ hiển thị cho bạn phép toán thập phân (-) cho 2 giá trị bit: AB (chỉ khi A chứa B)

hoạt động này là cần thiết khi chúng ta giữ một động từ trong chương trình biểu thị các bit. đôi khi chúng ta cần thêm bit (như trên) và đôi khi chúng ta cần loại bỏ bit (nếu động từ chứa then)

111 #decimal 7
-
100 #decimal 4
--------------
011 #decimal 3

với python: 7 & ~ 4 = 3 (xóa từ 7 bit đại diện cho 4)

001 #decimal 1
-
100 #decimal 4
--------------
001 #decimal 1

với python: 1 & ~ 4 = 1 (xóa khỏi 1 các bit đại diện cho 4 - trong trường hợp này là 1 không phải là 'chứa' 4) ..


0

Trong khi thao tác các bit của một số nguyên là hữu ích, thường đối với các giao thức mạng, có thể được chỉ định xuống từng bit, người ta có thể yêu cầu thao tác với các chuỗi byte dài hơn (không dễ dàng chuyển đổi thành một số nguyên). Trong trường hợp này, rất hữu ích khi sử dụng thư viện chuỗi bit cho phép thực hiện các hoạt động theo bit trên dữ liệu - ví dụ: người ta có thể nhập chuỗi 'ABCDEFGHIJKLMNOPQ' dưới dạng chuỗi hoặc dưới dạng hex và dịch chuyển bit (hoặc thực hiện các hoạt động theo bit khác):

>>> import bitstring
>>> bitstring.BitArray(bytes='ABCDEFGHIJKLMNOPQ') << 4
BitArray('0x142434445464748494a4b4c4d4e4f50510')
>>> bitstring.BitArray(hex='0x4142434445464748494a4b4c4d4e4f5051') << 4
BitArray('0x142434445464748494a4b4c4d4e4f50510')

0

các toán tử bitwise sau: & , |Các giá trị trả về , ^ , và ~ (dựa trên đầu vào của chúng) giống như cách cổng logic ảnh hưởng đến tín hiệu. Bạn có thể sử dụng chúng để mô phỏng mạch.


0

Để lật các bit (tức là phần bổ sung / đảo ngược của 1), bạn có thể làm như sau:

Vì giá trị ExORed với tất cả các kết quả là 1s đều có thể đảo ngược, nên đối với một độ rộng bit nhất định, bạn có thể sử dụng ExOR để đảo ngược chúng.

In Binary
a=1010 --> this is 0xA or decimal 10
then 
c = 1111 ^ a = 0101 --> this is 0xF or decimal 15
-----------------
In Python
a=10
b=15
c = a ^ b --> 0101
print(bin(c)) # gives '0b101'
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.