Toán tử bit tốt cho cái gì? [đóng cửa]


19

Các ngôn ngữ lập trình thường đi kèm với các toán tử bit khác nhau (ví dụ: dịch chuyển bit trái và phải, bitwise AND, OR, XOR ...). Chúng không được sử dụng mặc dù rất nhiều, hoặc ít nhất là như vậy có kinh nghiệm của tôi. Chúng đôi khi được sử dụng trong các thách thức lập trình hoặc câu hỏi phỏng vấn, hoặc giải pháp migh yêu cầu chúng, ví dụ:

  • Không sử dụng bất kỳ toán tử đẳng thức nào, hãy tạo một hàm trả về truekhi hai giá trị bằng nhau
  • Không sử dụng biến thứ ba, trao đổi giá trị của hai biến

Những điều này sau đó một lần nữa, có thể có ít sử dụng thế giới thực . Tôi đoán rằng họ nên nhanh hơn vì họ trực tiếp thao tác bộ nhớ ở mức thấp.

Tại sao như vậy được tìm thấy trong hầu hết các ngôn ngữ lập trình? Bất kỳ trường hợp sử dụng thế giới thực?


@Anto - Một ví dụ dễ dàng sẽ gửi dữ liệu trị giá 256Kb với tốc độ 256 từ một lần (4096 byte) cho khách hàng.
Ramhound

1
"Không sử dụng bất kỳ toán tử đẳng thức nào, hãy tạo một hàm trả về giá trị đúng khi hai giá trị bằng nhau" - trong C : return !(x-y);? Tôi không biết
Andrew Arnold

@Andrew: Đó là một giải pháp, nhưng bạn cũng có thể làm điều đó với các toán tử bitwise.
Anto

19
"Những thứ này không được sử dụng mặc dù rất nhiều" - Chắc chắn về điều đó? Tôi sử dụng tất cả thời gian. Chúng tôi không làm việc trong miền vấn đề của bạn.
Ed S.

2
Không đủ cho một câu trả lời đầy đủ, nhưng hãy thử đọc 4 bit trên cùng của một byte mà không cần xử lý bit và sau đó xem xét rằng một số định dạng dữ liệu được đóng gói rất chặt chẽ.
Phục hồi lại

Câu trả lời:


53

Không, chúng có nhiều ứng dụng trong thế giới thực và là các hoạt động cơ bản trên máy tính.

Chúng được sử dụng cho

  • Việc tung các khối byte xung quanh không phù hợp với kiểu dữ liệu ngôn ngữ lập trình
  • Chuyển đổi mã hóa qua lại từ lớn đến cuối endian.
  • Đóng gói 4 mẩu dữ liệu 6 bit thành 3 byte cho một số kết nối nối tiếp hoặc usb
  • Nhiều định dạng hình ảnh có số lượng bit khác nhau được gán cho mỗi kênh màu.
  • Bất cứ điều gì liên quan đến chân IO trong các ứng dụng nhúng
  • Nén dữ liệu, thường không có dữ liệu phù hợp với các ranh giới 8 bit đẹp. \
  • Các thuật toán băm, CRC hoặc kiểm tra tính toàn vẹn dữ liệu khác.
  • Mã hóa
  • Thế hệ số Psuedorandom
  • Raid 5 sử dụng XOR bitwise giữa các âm lượng để tính toán chẵn lẻ.
  • Tấn nhiều hơn

Trên thực tế, về mặt logic, tất cả các hoạt động trên máy tính cuối cùng đều có sự kết hợp của các hoạt động bitwise mức thấp này, diễn ra trong các cổng điện của bộ xử lý.


1
+1 cho danh sách khá toàn diện của bạn, mà bạn thậm chí dường như đang thêm vào
Anto

28
+1. @Anto: Danh sách này không ở đâu gần toàn diện. Một danh sách toàn diện các trường hợp sử dụng cho các toán tử bitwise trong lập trình hệ thống sẽ miễn là một danh sách toàn diện cho các truy vấn SQL trong các ứng dụng kinh doanh. Sự thật thú vị: Tôi luôn sử dụng các thao tác bitwise, nhưng đã không viết một câu lệnh SQL trong nhiều năm ;-)
nikie

4
@nikie: Và tôi viết SQL mọi lúc, nhưng đã không sử dụng các toán tử bitwise trong nhiều năm! :)
Thất vọngWithFormsDesigner

3
Tôi làm việc trong các hệ thống nhúng - toán tử bitwise là bánh mì và bơ. Được sử dụng hàng ngày mà không có bất kỳ suy nghĩ nào cả.
quick_now

9
Nếu tôi thỉnh thoảng sử dụng bithifting trong SQL, tôi có nhận được giải thưởng không?
Kiến

13

Bởi vì chúng là hoạt động cơ bản.

Cùng một dòng suy nghĩ, bạn có thể lập luận rằng phép cộng có ít cách sử dụng trong thế giới thực, vì nó có thể được thay thế hoàn toàn bằng phép trừ (và phủ định) và phép nhân. Nhưng chúng tôi tiếp tục bổ sung vì đó là một hoạt động cơ bản.

Và đừng nghĩ rằng chỉ vì bạn chưa thấy cần nhiều thao tác bitwise không có nghĩa là chúng không được sử dụng thường xuyên. Thật vậy, tôi đã sử dụng ops bitwise trong gần như mọi ngôn ngữ mà tôi đã sử dụng cho những thứ như mặt nạ bit.

Ngoài đỉnh đầu, tôi đã sử dụng các bit bit để xử lý hình ảnh, bitfield và cờ, xử lý văn bản (ví dụ: tất cả các ký tự của một lớp cụ thể thường chia sẻ một mẫu bit chung), mã hóa và giải mã dữ liệu tuần tự, giải mã VM hoặc CPU opcodes, và như vậy. Nếu không có bitwise, hầu hết các tác vụ này sẽ đòi hỏi các thao tác phức tạp hơn nhiều lần để thực hiện nhiệm vụ ít tin cậy hơn hoặc khả năng đọc kém hơn.

Ví dụ:

// Given a 30-bit RGB color value as a 32-bit int
// A lot of image sensors spit out 10- or 12-bit data
// and some LVDS panels have a 10- or 12-bit format
b = (color & 0x000003ff);
g = (color & 0x000ffc00) >> 10;
r = (color & 0x3ff00000) >> 20;

// Going the other way:
color = ((r << 20) & 0x3ff00000) | ((g << 10) & 0x000ffc00) | (b & 0x000003ff);

Giải mã các hướng dẫn CPU cho CPU loại RISC (chẳng hạn như khi mô phỏng nền tảng khác) yêu cầu trích xuất các phần có giá trị lớn như trên. Đôi khi, thực hiện các thao tác này với phép nhân và phép chia và modulo, v.v., có thể chậm hơn gấp mười lần so với các bit tương đương.


12

Một ví dụ điển hình là trích xuất các màu riêng lẻ từ giá trị RGB 24 bit và ngược lại.


EDIT: Từ http://www.docjar.com/html/api/java/awt/Color.java.html

    value =  ((int)(frgbvalue[2]*255 + 0.5))    |
                (((int)(frgbvalue[1]*255 + 0.5)) << 8 )  |
                (((int)(frgbvalue[0]*255 + 0.5)) << 16 ) |
                (((int)(falpha*255 + 0.5)) << 24 );

Chỉ ra ví dụ này trong thực tế? Một đoạn mã?
Anto

Một ví dụ tốt hơn có thể là xử lý các giá trị RGB 16 bit (4.5bpc) hoặc 30 bit (10bpc).
greyfade

@grey, cứ thoải mái thêm ví dụ như vậy.

6

Đây là một ví dụ trong thế giới thực mà bạn sẽ tìm thấy trong Quake 3, Quake 4. Doom III. Tất cả những trò chơi sử dụng công cụ Q3 .

float Q_rsqrt( float number )
{
        long i;
        float x2, y;
        const float threehalfs = 1.5F;

        x2 = number * 0.5F;
        y  = number;
        i  = * ( long * ) &y;                       // evil floating point bit level hacking [sic]
        i  = 0x5f3759df - ( i >> 1 );               // what the fuck? [sic]
        y  = * ( float * ) &i;
        y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
//    y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

        return y;
}

(Để hiểu mã đó, bạn cần hiểu cách lưu trữ số dấu phẩy động, tôi chắc chắn không thể giải thích về điều đó)

Về mặt sử dụng, trừ khi bạn ở trong các lĩnh vực yêu cầu dịch chuyển bit như mạng hoặc đồ họa, thì bạn có thể thấy mục đích của họ hơi hàn lâm. Nhưng vẫn thú vị (với tôi ít nhất).


+1 Đối với những bình luận đó, ngay cả khi chúng không phải là của bạn. Làm tôi cười thầm.
Bassinator

4

Dịch chuyển nhanh hơn nhân hoặc chia cho một sức mạnh của hai. Ví dụ: << = 2 nhân a với 4. Ngược lại, a >> = 2 chia a cho bốn. Người ta cũng có thể bit-bang dữ liệu ra một thiết bị bằng cách sử dụng các toán tử bit-khôn ngoan. Ví dụ: chúng ta có thể gửi N luồng dữ liệu nối tiếp ra khỏi cổng N pin bằng cách sử dụng các thao tác shift, xor và "và" bên trong các vòng lặp N. Bất cứ điều gì có thể được thực hiện trong logic kỹ thuật số cũng có thể được thực hiện trên phần mềm và ngược lại.


1
Chỉ cần cẩn thận khi chia với làm tròn lên hoặc xuống, v.v ... Việc dịch chuyển không tính đến điều đó, vì vậy tôi đã thấy thực sự tốt hơn khi sử dụng cách chia mã khi tôi có nghĩa là chia và để trình biên dịch tối ưu hóa nó thành một ca và thêm cho tôi.
Daemin

@Daemin: Tôi đang làm việc với các số nguyên khi tôi sử dụng kỹ thuật này. Hành vi mặc định cho phân chia số nguyên trong C và C ++ là cắt ngắn về 0; do đó, dịch chuyển một số nguyên phải bằng lũy ​​thừa hai sẽ tạo ra kết quả giống như chia một số nguyên cho lũy thừa hai.
bit-twiddler

1
@ bit-twiddler Dịch chuyển phải không hoạt động giống như cách chia cho số âm.
Daemin

@Daemin: Bạn dường như là địa ngục cúi đầu chứng minh tôi sai. Đầu tiên, bạn đưa ra vấn đề làm tròn số. Khi tôi từ chối yêu cầu đó bằng cách nêu phân chia đó trong C và C ++ cắt ngắn về 0, bạn sẽ loại bỏ vấn đề số nguyên đã ký. Tôi đã nói rằng tôi đang áp dụng toán tử ca ở đâu để ký hai số nguyên âm bổ sung? Như đã nói, người ta vẫn có thể sử dụng toán tử dịch chuyển để chia cho một lũy thừa hai. Tuy nhiên, vì C và C ++ thực hiện và dịch chuyển phải số học thay vì dịch chuyển phải cũ đơn giản, trước tiên người ta phải kiểm tra xem giá trị có âm hay không. Nếu giá trị là âm,
bit-twiddler

1
Chính xác, hãy cẩn thận khi sử dụng dịch chuyển thay thế cho phép nhân và chia vì có sự khác biệt tinh tế. Không nhiều không ít.
Daemin

3

Từ lâu, các toán tử bit rất hữu ích. Hôm nay họ ít như vậy. Ồ, chúng không hoàn toàn vô dụng, nhưng đã lâu rồi tôi mới thấy một chiếc được sử dụng nên được sử dụng.

Năm 1977 tôi là một lập trình viên ngôn ngữ lắp ráp. Tôi đã bị thuyết phục lắp ráp là ngôn ngữ thực sự duy nhất. Tôi chắc chắn rằng ngôn ngữ như Pascal là dành cho những kẻ say mê học thuật, những người không bao giờ phải làm bất cứ điều gì thực sự .

Sau đó tôi đọc "Ngôn ngữ lập trình C" của Kernighan và Ritchie. Nó thay đổi hoàn toàn suy nghĩ của tôi. Nguyên nhân? Nó có các toán tử bit! Đó một ngôn ngữ lắp ráp! Nó chỉ có một cú pháp khác nhau.

Quay lại những ngày đó tôi không thể hình dung được việc viết mã mà không có ands, ors, shift và xoay. Ngày nay tôi gần như không bao giờ sử dụng chúng.

Vì vậy, câu trả lời ngắn cho câu hỏi của bạn là: "Không có gì." Nhưng điều đó không hoàn toàn công bằng. Vì vậy, câu trả lời dài hơn là: "Chủ yếu là không có gì."


xkcd.com/378 đến với tâm trí.
Tối đa

Các nhà khai thác bit rất hữu ích cho đến ngày nay. Thực tế là trong miền của bạn, chúng không được sử dụng không làm cho nó không được sử dụng hoặc thậm chí không được sử dụng thường xuyên. Đây là một ví dụ đơn giản: thử và triển khai AES mà không cần toán tử bit. Đó là một ví dụ điển hình về một thứ gì đó được thực hiện trên hầu hết các máy tính hàng ngày hàng trăm hoặc hàng nghìn lần mỗi ngày.
CHỈ CẦN HOẠT ĐỘNG CỦA TÔI NGÀY

Mã hóa / giải mã dữ liệu mà không sử dụng các toán tử bit-khôn là tốt nhất. Ví dụ: việc thêm tệp đính kèm MIME vào tin nhắn yêu cầu chúng tôi có thể xử lý mã hóa dữ liệu ba đến bốn (hay còn gọi là mã hóa radix64).
bit-twiddler

2

Mã hóa

Tôi khuyên bạn nên xem một đoạn rất nhỏ từ thuật toán mã hóa DES :

temp = ((left >>> 1) ^ right) & 0x55555555; right ^= temp; left ^= (temp << 1);
temp = ((right >>> 8) ^ left) & 0x00ff00ff; left ^= temp; right ^= (temp << 8);
temp = ((right >>> 2) ^ left) & 0x33333333; left ^= temp; right ^= (temp << 2);
temp = ((left >>> 16) ^ right) & 0x0000ffff; right ^= temp; left ^= (temp << 16);
temp = ((left >>> 4) ^ right) & 0x0f0f0f0f; right ^= temp; left ^= (temp << 4);

Mặc dù không chắc chắn DES được khuyến nghị trong những ngày này: P
Armand

@ Alison: Không, nhưng các thuật toán mã hóa đã thay thế nó liên quan đến các hoạt động thao tác nhiều bit hơn, tôi nghĩ vậy. :-)
Carson63000

@ Alison - tất nhiên, nhưng TripleDES chỉ được thực hiện 3 lần với 3 lần các bit chính.
Scott Whitlock

2

Rất nhiều câu trả lời hay, vì vậy tôi sẽ không lặp lại những cách sử dụng đó.

Tôi sử dụng chúng khá nhiều trong mã được quản lý (C # / .Net) và không liên quan gì đến việc tiết kiệm không gian, hiệu suất cao hoặc thuật toán dịch chuyển bit thông minh. Đôi khi một số logic chỉ phù hợp để lưu trữ dữ liệu theo cách này. Tôi thường sử dụng chúng khi tôi có enum nhưng các thể hiện có thể đồng thời lấy nhiều giá trị từ enum đó. Tôi không thể đăng một ví dụ về mã từ công việc, nhưng google nhanh cho "Cờ enum" ("Cờ" là cách xác định C # để sử dụng enum theo cách bitwise) đưa ra ví dụ hay này: http: // www.dotnetperls.com/enum-flags .


2

Ngoài ra còn có bit tính toán song song. Nếu dữ liệu của bạn chỉ là 1 và 0, bạn có thể gói 64 trong số chúng thành một từ dài không dấu và có được 64 thao tác song song. Thông tin di truyền là hai bit (đại diện cho mã hóa AGCT của DNA) và nếu bạn có thể thực hiện các tính toán khác nhau theo kiểu song song bit, bạn có thể làm được nhiều hơn so với nếu không. Chưa kể mật độ dữ liệu trong bộ nhớ - nếu bộ nhớ, hoặc dung lượng ổ đĩa, hoặc băng thông truyền thông bị hạn chế ngụ ý rằng việc nén / giải nén nên được xem xét. Ngay cả các số nguyên có tỷ lệ thấp, xuất hiện trong các lĩnh vực như xử lý hình ảnh, có thể tận dụng tính toán song song bit khó khăn. Đó là một toàn bộ nghệ thuật cho chính nó.


1

Tại sao chúng được tìm thấy?

Vâng, đó có lẽ là vì chúng tương ứng với hướng dẫn lắp ráp và đôi khi chúng chỉ hữu ích cho những thứ ở ngôn ngữ cấp cao hơn. Điều tương tự áp dụng cho sự sợ hãi GOTOtương ứng với JMPhướng dẫn lắp ráp.

Công dụng của chúng là gì?

Thực sự có rất nhiều cách sử dụng để đặt tên, vì vậy tôi sẽ chỉ đưa ra một cách sử dụng gần đây, mặc dù được địa phương hóa cao. Tôi làm việc rất nhiều với lắp ráp 6502 và tôi đang làm việc trên một ứng dụng nhỏ chuyển đổi địa chỉ bộ nhớ, giá trị, so sánh giá trị, v.v. thành mã có thể được sử dụng cho thiết bị GameGenie (Về cơ bản là ứng dụng gian lận cho NES). Các mã được tạo ra bởi một số thao tác bit.


1

Nhiều lập trình viên ngày nay đã quen với các máy tính có bộ nhớ gần vô hạn.

Nhưng một số sử dụng vẫn lập trình các bộ vi điều khiển nhỏ trong đó mỗi bit đều có giá trị (ví dụ khi bạn chỉ có 1k RAM trở xuống) và các toán tử bitwise cho phép lập trình viên sử dụng các bit đó một lần thay vì lãng phí một số chương trình lớn hơn nhiều thực thể trừu tượng hơn có thể cần thiết để giữ một số trạng thái theo yêu cầu của thuật toán. IO trên các thiết bị đó cũng có thể yêu cầu được đọc hoặc điều khiển trên cơ sở bitwise.

"Thế giới thực" có nhiều vi điều khiển nhỏ hơn nhiều so với máy chủ hoặc PC.

Đối với các loại CS lý thuyết thuần túy, máy Turing là tất cả về các bit trạng thái.


1

Chỉ một trong nhiều cách sử dụng có thể của các toán tử bitwise ...

Các toán tử bitwise cũng có thể giúp làm cho mã của bạn dễ đọc hơn. Hãy xem xét khai báo hàm sau ....

int  myFunc (bool, bool, bool, bool, bool, bool, bool, bool);

...

myFunc (false, true, false, false, false, true, true, false);

Rất dễ quên tham số boolean có nghĩa là gì khi viết hoặc thậm chí đọc mã. Nó cũng dễ dàng để mất theo dõi đếm của bạn. Một thói quen như vậy có thể được làm sạch.

/* More descriptive names than MY_FLAGx would be better */
#define MY_FLAG1    0x0001
#define MY_FLAG2    0x0002
#define MY_FLAG3    0x0004
#define MY_FLAG4    0x0008
#define MY_FLAG5    0x0010
#define MY_FLAG6    0x0020
#define MY_FLAG7    0x0040
#define MY_FLAG8    0x0080

int  myFunc (unsigned myFlags);

...

myFunc (MY_FLAG2 | MY_FLAG6 | MY_FLAG7);

Với các tên cờ mô tả nhiều hơn, nó trở nên dễ đọc hơn nhiều.


1

Nếu bạn biết bất cứ điều gì về Unicode , có lẽ bạn đã quen thuộc với UTF-8. Nó sử dụng một loạt các bài kiểm tra bit, dịch chuyển và mặt nạ để gói điểm mã 20 bit thành 1 đến 4 byte.


0

Tôi không sử dụng chúng thường xuyên nhưng đôi khi chúng có ích. Enum xử lý đến tâm trí.

Thí dụ:

enum DrawBorder{None = 0, Left = 1, Top = 2, Right = 4, Bottom = 8}

DrawBorder drawBorder = DrawBorder.Left | DrawBorder.Right;//Draw right & left border
if(drawBorder & DrawBorder.Left == DrawBorder.Left)
  //Draw the left border
if(drawBorder & DrawBorder.Top == DrawBorder.Top)
  //Draw the top border
//...

0

Không chắc chắn nếu việc sử dụng này đã được ghi nhận chưa:

Tôi thấy HOẶC khá nhiều khi làm việc với mã nguồn illumos (openSolaris) để giảm nhiều giá trị trả về xuống 0 hoặc 1, ví dụ:

int ret = 0;
ret |= some_function(arg, &arg2); // returns 0 or 1
ret |= some_other_function(arg, &arg2); // returns 0 or 1
return ret;
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.