các ứng dụng thực tế của hoạt động bitwise [đóng]


79
  1. Bạn đã sử dụng các phép toán bitwise để làm gì?
  2. tại sao chúng rất tiện dụng?
  3. ai đó có thể vui lòng giới thiệu một hướng dẫn RẤT đơn giản không?

Câu trả lời:


83

Mặc dù mọi người dường như đều bị mắc kẹt vào usecase cờ, nhưng đó không phải là ứng dụng duy nhất của toán tử bitwise (mặc dù có lẽ là phổ biến nhất). Ngoài ra C # là một ngôn ngữ cấp đủ cao mà các kỹ thuật khác có thể sẽ hiếm khi được sử dụng, nhưng bạn vẫn nên biết về chúng. Đây là những gì tôi có thể nghĩ đến:


Các toán tử <<>>có thể nhanh chóng nhân với lũy thừa của 2. Tất nhiên, trình tối ưu hóa .NET JIT có thể sẽ làm điều này cho bạn (và bất kỳ trình biên dịch tốt nào của ngôn ngữ khác), nhưng nếu bạn thực sự băn khoăn về từng micro giây, bạn chỉ có thể viết điều này để chắc chắn.

Một cách sử dụng phổ biến khác cho các toán tử này là nhồi hai số nguyên 16 bit vào một số nguyên 32 bit. Giống:

int Result = (shortIntA << 16 ) | shortIntB;

Điều này thường xảy ra khi giao tiếp trực tiếp với các chức năng của Win32, đôi khi sử dụng thủ thuật này vì những lý do cũ.

Và, tất nhiên, các toán tử này rất hữu ích khi bạn muốn đánh lừa những người chưa có kinh nghiệm, như khi cung cấp câu trả lời cho một câu hỏi bài tập về nhà. :)

Trong bất kỳ mã thực nào, mặc dù bạn sẽ tốt hơn nhiều bằng cách sử dụng phép nhân thay thế, bởi vì nó có khả năng đọc tốt hơn nhiều và JIT tối ưu hóa nó shlvà các shrhướng dẫn nên không bị phạt về hiệu suất.


Khá nhiều thủ thuật kỳ lạ đối phó với ^nhà điều hành (XOR). Đây thực sự là một toán tử rất mạnh mẽ, vì các thuộc tính sau:

  • A^B == B^A
  • A^B^A == B
  • Nếu bạn biết A^Bthì không thể biết được cái gì Avà cái gì Bđang có, nhưng nếu bạn biết một trong số chúng, bạn có thể tính toán cái còn lại.
  • Toán tử không bị bất kỳ lỗi tràn nào như nhân / chia / cộng / trừ.

Một vài thủ thuật tôi đã thấy khi sử dụng toán tử này:

Trao đổi hai biến số nguyên mà không có biến trung gian:

A = A^B // A is now XOR of A and B
B = A^B // B is now the original A
A = A^B // A is now the original B

Danh sách được liên kết kép chỉ với một biến bổ sung cho mỗi mục. Điều này sẽ ít được sử dụng trong C #, nhưng nó có thể hữu ích cho lập trình cấp thấp của các hệ thống nhúng trong đó mỗi byte đều được tính.

Ý tưởng là bạn theo dõi con trỏ cho mục đầu tiên; con trỏ cho mục cuối cùng; và đối với mọi mục bạn theo dõi pointer_to_previous ^ pointer_to_next. Bằng cách này, bạn có thể duyệt qua danh sách từ một trong hai đầu, tuy nhiên chi phí chỉ bằng một nửa so với danh sách được liên kết truyền thống. Đây là mã C ++ để duyệt:

ItemStruct *CurrentItem = FirstItem, *PreviousItem=NULL;
while (  CurrentItem != NULL )
{
    // Work with CurrentItem->Data

    ItemStruct *NextItem = CurrentItem->XorPointers ^ PreviousItem;
    PreviousItem = CurrentItem;
    CurrentItem = NextItem;
}

Để đi qua từ cuối, bạn chỉ cần thay đổi dòng đầu tiên từ FirstItemthành LastItem. Đó là một tiết kiệm bộ nhớ khác ngay tại đó.

Một nơi khác mà tôi sử dụng ^toán tử thường xuyên trong C # là khi tôi phải tính toán Mã HashCode cho kiểu của mình, đó là kiểu kết hợp. Giống:

class Person
{
    string FirstName;
    string LastName;
    int Age;

    public int override GetHashCode()
    {
        return (FirstName == null ? 0 : FirstName.GetHashCode()) ^
            (LastName == null ? 0 : LastName.GetHashCode()) ^
            Age.GetHashCode();
    }
}

13
+1, hoán đổi xor đẹp. Cảm ơn bạn.
Grozz

4
+1 cho phương pháp xor-list, chưa từng thấy trước đây.
Snemarch

2
thú vị mà điều này đã được dán nhãn không mang tính xây dựng :)
Alex Gordon

"khi bạn muốn nhầm lẫn" +1 thiếu kinh nghiệm "cho việc đóng đinh MO của quá nhiều nhà phát triển / kiến ​​trúc sư cấp cao. Tôi thực sự nghĩ rằng với kinh nghiệm sẽ trở thành chủ nghĩa thực dụng khiêm tốn, nhưng không. Các nhà môi giới đã tiếp quản và thế giới còn tệ hơn vì điều đó.
Davos

và +1000 này "Trong bất kỳ mã thực nào, thay vào đó bạn sẽ tốt hơn nhiều bằng cách sử dụng phép nhân, bởi vì nó có khả năng đọc tốt hơn nhiều và JIT tối ưu hóa nó để shl và shr hướng dẫn nên không bị phạt về hiệu suất." Tại sao mã golf lại thịnh hành trong mã thực?
Davos

74

Tôi sử dụng toán tử bitwise để bảo mật trong các ứng dụng của mình. Tôi sẽ lưu trữ các cấp độ khác nhau bên trong Enum:

[Flags]
public enum SecurityLevel
{
    User = 1, // 0001
    SuperUser = 2, // 0010
    QuestionAdmin = 4, // 0100
    AnswerAdmin = 8 // 1000
}

Và sau đó chỉ định cho người dùng các cấp của họ:

// Set User Permissions to 1010
//
//   0010
// | 1000
//   ----
//   1010
User.Permissions = SecurityLevel.SuperUser | SecurityLevel.AnswerAdmin;

Và sau đó kiểm tra các quyền trong hành động đang được thực hiện:

// Check if the user has the required permission group
//
//   1010
// & 1000
//   ----
//   1000
if( (User.Permissions & SecurityLevel.AnswerAdmin) == SecurityLevel.AnswerAdmin )
{
    // Allowed
}

1
@justin, cảm ơn bạn rất nhiều. bạn có thể vui lòng giải thích User này không.Permissions = SecurityLevel.SuperUser | SecurityLevel.AnswerAdmin;
Alex Gordon

Đây chỉ là những enums được gắn cờ.
Dave

@jenny - Đã thêm làm rõ trong các nhận xét mã.
Justin Niessner 7/10/10

1
@Dave - Chính xác. Nhưng khi bạn sử dụng các Enums được gắn cờ, bạn đang sử dụng các Toán tử Bitwise để kiểm tra các giá trị. Tiện dụng và khá đơn giản. Theo tôi, chính xác những gì OP yêu cầu.
Justin Niessner

7
@Justin: Mặc dù bây giờ đã lỗi thời, Enum.HasFlag đưa ra: msdn.microsoft.com/en-us/library/system.enum.hasflag.aspx
Reed Copsey

16

Tôi không biết việc giải một sudoku mà bạn cho là thực tế như thế nào, nhưng hãy giả sử nó là như vậy.

Hãy tưởng tượng bạn muốn viết một bộ giải sudoku hoặc thậm chí chỉ là một chương trình đơn giản, hiển thị cho bạn bảng và cho phép bạn tự giải câu đố, nhưng đảm bảo các nước đi là hợp pháp.

Bản thân bảng có lẽ sẽ được biểu diễn bằng một mảng hai chiều như:

uint [, ] theBoard = new uint[9, 9];

Giá trị 0có nghĩa là ô vẫn còn trống và các giá trị từ phạm vi [1u, 9u] là giá trị thực trong bảng.

Bây giờ hãy tưởng tượng bạn muốn kiểm tra xem một số động thái có hợp pháp hay không. Rõ ràng là bạn có thể làm điều đó với một vài vòng lặp, nhưng mặt nạ bit cho phép bạn làm mọi thứ nhanh hơn nhiều. Trong một chương trình đơn giản, chỉ cần đảm bảo các quy tắc được tuân theo, điều đó không quan trọng, nhưng trong một trình giải thì điều đó có thể.

Bạn có thể duy trì các mảng mặt nạ bit, lưu trữ thông tin về các số đã được chèn trong mỗi hàng, mỗi cột a và mỗi hộp 3x3.

uint [] maskForNumbersSetInRow = new uint[9];

uint [] maskForNumbersSetInCol = new uint[9];

uint [, ] maskForNumbersSetInBox = new uint[3, 3];

Ánh xạ từ số đến bitpattern, với một bit tương ứng với tập số đó, rất đơn giản

1 -> 00000000 00000000 00000000 00000001
2 -> 00000000 00000000 00000000 00000010
3 -> 00000000 00000000 00000000 00000100
...
9 -> 00000000 00000000 00000001 00000000

Trong C #, bạn có thể tính bitpattern theo cách này ( valuelà một uint):

uint bitpattern = 1u << (int)(value - 1u);

Trong dòng trên 1utương ứng với bitpattern 00000000 00000000 00000000 00000001được dịch chuyển sang trái value - 1. Ví dụ value == 5, nếu bạn nhận được

00000000 00000000 00000000 00010000

Lúc đầu, mặt nạ cho mỗi hàng, cột và hộp là 0. Mỗi khi bạn đặt một số lên bảng, bạn cập nhật mặt nạ, do đó, bit tương ứng với giá trị mới được đặt.

Giả sử bạn chèn giá trị 5 vào hàng 3 (các hàng và cột được đánh số từ 0). Mặt nạ cho hàng 3 được lưu trữ trong maskForNumbersSetInRow[3]. Cũng giả sử rằng trước khi chèn đã có các số {1, 2, 4, 7, 9}ở hàng 3. Mẫu bit trong mặt nạ maskForNumbersSetInRow[3]trông giống như sau:

00000000 00000000 00000001 01001011
bits above correspond to:9  7  4 21

Mục đích là đặt bit tương ứng với giá trị 5 trong mặt nạ này. Bạn có thể làm điều đó bằng cách sử dụng bitwise hoặc toán tử ( |). Đầu tiên, bạn tạo một mẫu bit tương ứng với giá trị 5

uint bitpattern = 1u << 4; // 1u << (int)(value - 1u)

và sau đó bạn sử dụng operator |để đặt bit trong mặt nạmaskForNumbersSetInRow[3]

maskForNumbersSetInRow[3] = maskForNumbersSetInRow[3] | bitpattern;

hoặc sử dụng mẫu ngắn hơn

maskForNumbersSetInRow[3] |= bitpattern;

00000000 00000000 00000001 01001011
                 |
00000000 00000000 00000000 00010000
                 =
00000000 00000000 00000001 01011011

Bây giờ mặt nạ của bạn chỉ ra rằng có các giá trị {1, 2, 4, 5, 7, 9}trong hàng này (hàng 3).

Nếu bạn muốn kiểm tra, nếu một số giá trị nằm trong hàng, bạn có thể sử dụng operator &để kiểm tra xem bit tương ứng có được đặt trong mặt nạ hay không. Nếu kết quả của toán tử đó được áp dụng cho mặt nạ và một mẫu bit, tương ứng với giá trị đó, là khác 0, giá trị đó đã có trong hàng. Nếu kết quả là 0 thì giá trị không có trong hàng.

Ví dụ: nếu bạn muốn kiểm tra xem giá trị 3 có trong hàng hay không, bạn có thể thực hiện theo cách này:

uint bitpattern = 1u << 2; // 1u << (int)(value - 1u)
bool value3IsInRow = ((maskForNumbersSetInRow[3] & bitpattern) != 0);

00000000 00000000 00000001 01001011 // the mask
                 |
00000000 00000000 00000000 00000100 // bitpattern for the value 3
                 =
00000000 00000000 00000000 00000000 // the result is 0. value 3 is not in the row.

Dưới đây là các phương pháp để đặt giá trị mới trong bảng, duy trì cập nhật các mặt nạ bit thích hợp và để kiểm tra xem việc di chuyển có hợp pháp hay không.

public void insertNewValue(int row, int col, uint value)
{

    if(!isMoveLegal(row, col, value))
        throw ...

    theBoard[row, col] = value;

    uint bitpattern = 1u << (int)(value - 1u);

    maskForNumbersSetInRow[row] |= bitpattern;

    maskForNumbersSetInCol[col] |= bitpattern;

    int boxRowNumber = row / 3;
    int boxColNumber = col / 3;

    maskForNumbersSetInBox[boxRowNumber, boxColNumber] |= bitpattern;

}

Có mặt nạ, bạn có thể kiểm tra xem việc di chuyển như thế này có hợp pháp không:

public bool isMoveLegal(int row, int col, uint value)
{

    uint bitpattern = 1u << (int)(value - 1u);

    int boxRowNumber = row / 3;
    int boxColNumber = col / 3;

    uint combinedMask = maskForNumbersSetInRow[row] | maskForNumbersSetInCol[col]
                        | maskForNumbersSetInBox[boxRowNumber, boxColNumber];

    return ((theBoard[row, col] == 0) && ((combinedMask & bitpattern) == 0u);
}

3
Những thứ thực sự thú vị nhưng hơi bị thổi qua đầu tôi. Có vẻ như có thể có một chút xây dựng từ những gì đang diễn ra ở đây có thể theo thứ tự (một số nhận xét với các ví dụ nhỏ). Sự thay đổi nhỏ trong việc xây dựng mặt nạ và như vậy là một chút bối rối với tôi. Chỉ hỏi vì bạn rõ ràng đã dành chút thời gian cho câu trả lời.
Đánh dấu


3

Nếu bạn cần giao tiếp với phần cứng, bạn sẽ cần sử dụng bit twiddling tại một số điểm.

Trích xuất các giá trị RGB của một giá trị pixel.

Rất nhiều điều


2
  1. Chúng có thể được sử dụng để truyền nhiều đối số cho một hàm thông qua một biến kích thước giới hạn.
  2. Ưu điểm là chi phí bộ nhớ thấp, hoặc chi phí bộ nhớ thấp: Do đó tăng hiệu suất.
  3. Tôi không thể viết một bài hướng dẫn ngay tại chỗ, nhưng tôi chắc chắn rằng họ có sẵn.

rất tiếc, tôi vừa thấy bạn được gắn thẻ C # này, tôi nghĩ đó là C ++. ... phải đọc chậm ... :)
C Johnson

2

Chúng có thể được sử dụng cho toàn bộ tải các ứng dụng khác nhau, đây là câu hỏi tôi đã đăng trước đây ở đây, sử dụng các thao tác bitwise:

Câu hỏi Bitwise AND, Bitwise Inclusive OR, trong Java

Đối với các ví dụ khác, hãy xem (giả sử) liệt kê được gắn cờ.

Trong ví dụ của tôi, tôi đang sử dụng các phép toán bitwise để thay đổi phạm vi của một số nhị phân từ -128 ... 127 thành 0..255 (thay đổi biểu diễn của nó từ có dấu thành không dấu).

bài viết MSN ở đây ->

http://msdn.microsoft.com/en-us/library/6a71f45d%28VS.71%29.aspx

là hữu ích.

Và, mặc dù liên kết này:

http://weblogs.asp.net/alessandro/archive/2007/10/02/bitwise-operators-in-c-or-xor-and-amp-amp-not.aspx

rất kỹ thuật, nó bao gồm tất cả mọi thứ.

HTH


2

Bất cứ khi nào bạn có một tùy chọn của 1 hoặc nhiều hơn kết hợp các mục thì bitwise thường là một cách khắc phục dễ dàng.

Một số ví dụ bao gồm các bit bảo mật (đang chờ mẫu của Justin ..), lập lịch ngày, v.v.


2

Tôi phải nói rằng một trong những cách sử dụng phổ biến nhất là sửa đổi các trường bit để nén dữ liệu. Bạn hầu hết thấy điều này trong các chương trình cố gắng tiết kiệm với các gói.

Ví dụ về nén mạng bằng trường bit


2

Một trong những thứ thường xuyên nhất mà tôi sử dụng chúng trong C # là tạo mã băm. Có một số phương pháp băm hợp lý sử dụng chúng. Ví dụ: đối với một lớp phối hợp có X, Y là cả hai int mà tôi có thể sử dụng:

public override int GetHashCode()
{
  return x ^ ((y << 16) | y >> 16);
}

Điều này nhanh chóng tạo ra một số được đảm bảo là bằng nhau khi được tạo ra bởi một đối tượng bằng nhau (giả sử rằng bằng nhau có nghĩa là cả hai tham số X và Y đều giống nhau trong cả hai đối tượng được so sánh) trong khi cũng không tạo ra các mẫu xung đột cho các đối tượng có giá trị thấp (có khả năng là phổ biến nhất trong hầu hết các ứng dụng).

Một cách khác là kết hợp liệt kê cờ. Ví dụRegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase

Có một số hoạt động cấp thấp thường không cần thiết hơn khi bạn viết mã dựa trên một khuôn khổ như .NET (ví dụ: trong C #, tôi sẽ không cần viết mã để chuyển đổi UTF-8 thành UTF-16, nó có sẵn cho tôi trong khung), nhưng tất nhiên ai đó phải viết mã đó.

Có một vài kỹ thuật xoay bit, như làm tròn lên số nhị phân gần nhất (ví dụ: làm tròn 1010 thành 10000):

        unchecked
        {
            --x;
            x |= (x >> 1);
            x |= (x >> 2);
            x |= (x >> 4);
            x |= (x >> 8);
            x |= (x >> 16);
            return ++x;
        }

Cái nào hữu ích khi bạn cần, nhưng điều đó có xu hướng không phổ biến lắm.

Cuối cùng, bạn cũng có thể sử dụng chúng để tối ưu hóa toán học vi mô chẳng hạn như << 1thay vì * 2nhưng tôi bao gồm điều đó chỉ để nói rằng đó thường là một ý tưởng tồi vì nó ẩn ý định của mã thực, hầu như không tiết kiệm gì trong hiệu suất và có thể ẩn một số lỗi nhỏ. .


1
Về mặt kỹ thuật, các toán tử dịch chuyển bit không thể được coi là các toán tử khôn ngoan. Kiểm tra các phần chút ca của bài viết Wikipedia là tại sao: en.wikipedia.org/wiki/Bitwise_operation
Justin Niessner

1

Sắp xếp nhị phân. Đã có vấn đề trong đó việc triển khai sử dụng toán tử phân chia thay vì toán tử dịch chuyển bit. Điều này khiến BS không thành công sau khi bộ sưu tập có kích thước trên 10.000.000


1

Bạn sẽ sử dụng chúng vì nhiều lý do:

  • lưu trữ (và kiểm tra!) cờ tùy chọn theo cách hiệu quả về bộ nhớ
  • nếu thực hiện lập trình tính toán, bạn có thể muốn xem xét tối ưu hóa một số hoạt động của mình bằng cách sử dụng các phép toán bit thay vì các toán tử toán học (hãy cẩn thận với các tác dụng phụ)
  • Mã xám !
  • tạo các giá trị được liệt kê

Tôi chắc rằng bạn có thể nghĩ về những người khác.

Nói như vậy, đôi khi bạn cần tự hỏi: liệu việc tăng cường trí nhớ và hiệu suất có đáng để bạn nỗ lực không. Sau khi viết loại mã đó, hãy để nó nghỉ một lúc và quay lại với nó. Nếu bạn gặp khó khăn với nó, hãy viết lại bằng một đoạn mã dễ bảo trì hơn.

Mặt khác, đôi khi sử dụng các phép toán bitwise sẽ rất hợp lý (hãy nghĩ đến mật mã).

Tốt hơn hết, hãy để người khác đọc nó và ghi lại nhiều tài liệu.


1

Trò chơi!

Trước đây, tôi đã sử dụng nó để đại diện cho các quân cờ của người chơi Reversi. Đó là 8X8 nên tôi đã chọn một longloại, và hơn thế, chẳng hạn nếu bạn muốn biết tất cả các quân orcờ trên tàu ở đâu - cả hai người đều chơi.
Nếu bạn muốn tất cả các bước có thể có của một người chơi, hãy nói ở bên phải - bạn >>đại diện cho các quân của người chơi và ANDnó với các quân của đối thủ để kiểm tra xem bây giờ có quân 1 chung hay không (nghĩa là có quân đối thủ ở bên phải của bạn). Sau đó, bạn tiếp tục làm điều đó. nếu bạn quay trở lại các mảnh của riêng bạn - không di chuyển. Nếu bạn hiểu rõ một chút - bạn có thể di chuyển đến đó và chụp tất cả các mảnh trên đường đi.
(Kỹ thuật này được sử dụng rộng rãi, trong nhiều loại trò chơi trên bàn cờ, bao gồm cả cờ vua)

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.