按位运算的实际应用

时间:2010-10-07 15:44:43

标签: c# bitwise-operators

  1. 您使用了什么按位操作?
  2. 他们为什么这么方便?
  3. 有人可以推荐一个非常简单的教程吗?

13 个答案:

答案 0 :(得分:76)

虽然每个人似乎都迷上了标志用例,但这并不是按位运算符的唯一应用(虽然可能是最常见的)。此外,C#是一种足够高级别的语言,其他技术可能很少使用,但仍然值得了解它们。这是我能想到的:


<<>>运算符可以快速乘以2的幂。当然,.NET JIT优化器可能会为您(以及其他语言的任何体面编译器)执行此操作,但如果你真的每微秒都在烦恼,你可能会写这个以确定。

这些运算符的另一个常见用途是将两个16位整数填充为一个32位整数。像:

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

这对于与Win32函数的直接接口很常见,Win32函数有时会出于遗留原因使用此技巧。

当然,当你想让没有经验的人混淆时,这些算子很有用,就像提供家庭作业问题的答案一样。 :)

在任何实际代码中,尽管使用乘法会更好,但是因为它具有更好的可读性,并且JIT将其优化为shlshr指令,所以没有绩效惩罚。


^运算符(XOR)处理相当多的好奇技巧。由于以下属性,这实际上是一个非常强大的运算符:

  • A^B == B^A
  • A^B^A == B
  • 如果您知道A^B,则无法确定AB是什么,但如果您知道其中之一,则可以计算另一个。
  • 操作员不会遇到任何溢出,如乘法/除法/加法/减法。

我使用此运算符看到了几个技巧:

交换两个没有中间变量的整数变量:

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

双重链接列表,每个项目只有一个额外变量。这在C#中几乎没用,但它可能会对每个字节都很重要的嵌入式系统的低级编程有用。

这个想法是你跟踪第一个项目的指针;最后一项的指针;并且对于您跟踪pointer_to_previous ^ pointer_to_next的每个项目。这样您就可以从任一端遍历列表,但开销只是传统链表的一半。这是遍历的C ++代码:

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

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

要从最后遍历,您只需将第一行从FirstItem更改为LastItem即可。那是另一个记忆保存。

我在C#中定期使用^运算符的另一个地方是我必须为我的类型计算一个复合类型的HashCode。像:

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();
    }
}

答案 1 :(得分:66)

我在应用程序中使用按位运算符来提高安全性。我将在Enum中存储不同的级别:

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

然后为用户分配他们的级别:

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

然后检查正在执行的操作中的权限:

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

答案 2 :(得分:16)

我不知道解决你认为的数独是多么实际,但我们假设它是。

想象一下,你想要写一个数独求解器,甚至只是一个简单的程序,它会向你显示板子并让你自己解决这个难题,但确保这些动作是合法的。

董事会本身很可能由二维数组表示,如:

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

0表示单元格仍为空,范围[1u,9u]中的值是板中的实际值。

现在想象一下你想检查某些举动是否合法。显然你可以通过几个循环来完成它,但是bitmasks可以让你更快地完成任务。在一个只能确保遵守规则的简单程序中,它并不重要,但在求解器中它可以。

您可以维护位掩码数组,这些数组存储有关已插入每行,每列a和每个3x3框的数字的信息。

uint [] maskForNumbersSetInRow = new uint[9];

uint [] maskForNumbersSetInCol = new uint[9];

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

从数字到位模式的映射,其中一位对应于该数字集,非常简单

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

在C#中,您可以通过这种方式计算位模式(valueuint):

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

在对应于bitpattern 1u的上方00000000 00000000 00000000 00000001行中,value - 1向左移动。例如,如果value == 5,则获得

00000000 00000000 00000000 00010000

一开始,每行,列和框的掩码为0。每次在板上放一些数字时,都会更新掩码,因此设置了与新值对应的位。

假设您在第3行插入值5(行和列从0开始编号)。第3行的掩码存储在maskForNumbersSetInRow[3]中。我们还假设在插入之前,第3行中已经有数字{1, 2, 4, 7, 9}。掩码maskForNumbersSetInRow[3]中的位模式如下所示:

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

目标是在此掩码中设置与值5对应的位。您可以使用按位或运算符(|)来执行此操作。首先,创建一个与值5

对应的位模式
uint bitpattern = 1u << 4; // 1u << (int)(value - 1u)

然后使用operator |设置掩码maskForNumbersSetInRow[3]

中的位
maskForNumbersSetInRow[3] = maskForNumbersSetInRow[3] | bitpattern;

或使用更短的表格

maskForNumbersSetInRow[3] |= bitpattern;

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

现在,您的蒙版表示此行(第3行)中有值{1, 2, 4, 5, 7, 9}

如果要检查,如果行中有某个值,则可以使用operator &检查掩码中是否设置了相应的位。如果该运算符的结果应用于掩码,并且对应于该值的位模式不为零,则该值已在该行中。如果结果为0,则该值不在行中。

例如,如果要检查值3是否在行中,可以这样做:

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.

以下是在电路板中设置新值,保持适当的位屏最新以及检查移动是否合法的方法。

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;

}

拥有面具,您可以检查移动是否合法:

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 :(得分:4)

Dozens of bit twiddling examples here

代码在C中,但您可以轻松地将其调整为C#

答案 4 :(得分:3)

如果你需要与硬件进行通信,你需要在某些时候使用bit twiddling。

提取像素值的RGB值。

这么多东西

答案 5 :(得分:2)

它们可以用于不同应用程序的整个负载,这里是我之前发布的一个问题,它使用按位运算:

Bitwise AND, Bitwise Inclusive OR question, in Java

对于其他示例,请查看(例如)标记的枚举。

在我的示例中,我使用按位运算将二进制数的范围从-128 ... 127更改为0..255(将其表示从有符号更改为无符号)。

这里的MSN文章 - &gt;

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

很有用。

而且,虽然这个链接:

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

非常技术性,它涵盖了一切。

HTH

答案 6 :(得分:2)

任何时候你有一个或多个项目组合的选项,然后按位通常是一个简单的解决方案。

一些例子包括安全位(等待Justin的样本......),安排日期等等。

答案 7 :(得分:2)

我不得不说最常见的用途之一是修改位域来压缩数据。你经常在试图使用数据包的程序中看到这一点。

network compression using bitfields

的示例

答案 8 :(得分:2)

我在C#中使用它们的最常见的事情之一是生成哈希码。有一些相当不错的散列方法可以使用它们。例如。对于一个带有X和Y的坐标类,我可能会使用它们:

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

这会快速生成一个在由相等对象生成时保证相等的数字(假设相等意味着两个对象中的X和Y参数都相同),同时也不会为低值对象生成冲突模式(可能在大多数应用中最常见。)

另一种是组合标志枚举。例如。 RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase

当您对.NET这样的框架进行编码时,通常不需要进行一些低级操作(例如,在C#中,我不需要编写代码将UTF-8转换为UTF-16,它就在那里对我来说,在框架中),但当然有人必须编写该代码。

有一些比特麻烦的技术,比如四舍五入到最接近的二进制数(例如向上舍入1010到10000):

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

当你需要它们时它们很有用,但这种情况往往不常见。

最后,您还可以使用它们来微观优化数学,例如<< 1而不是* 2,但我只是说它通常是一个坏主意,因为它隐藏了真实代码的意图,几乎没有保存任何性能,并且可以隐藏一些微妙的错误。

答案 9 :(得分:1)

  1. 它们可用于通过一个有限大小的变量向函数传递许多参数。
  2. 优点是内存开销低或内存成本低:因此提高了性能。
  3. 我不能当场写一个教程,但他们在那里我很确定。

答案 10 :(得分:1)

二进制排序。存在实现使用除法运算符而不是位移运算符的问题。这导致BS在收集到大小超过10,000,000

之后失败

答案 11 :(得分:1)

您将出于各种原因使用它们:

  • 以内存有效的方式存储(并检查!)选项标志
  • 如果进行计算编程,您可能需要考虑通过使用按位运算而不是数学运算符来优化您的一些操作(注意副作用)
  • Gray Code <!/ LI>
  • 创建枚举值

我相信你能想到别人。

话虽这么说,有时你需要问问自己:记忆和性能的提升值得付出努力。在编写了那种代码后,让它休息一段时间然后再回过头来。如果您对此感到困难,请使用更易于维护的代码重写。

另一方面,有时使用按位操作(想想密码术)确实很有意义。

更好的是,让其他人阅读,并广泛记录。

答案 12 :(得分:1)

游戏!

在这些日子里,我用它来代表一个黑白棋手的作品。它是8X8,所以它花了我long类型,而且,例如,如果你想知道所有棋子在哪里 - 你or两个玩家的棋子。
如果你想要一个玩家的所有可能的步骤,请向右说 - 你>>玩家的棋子代表一个,AND它与对手的棋子一起检查现在是否有共同的1(这意味着你的右边有一个对手)。然后你继续这样做。如果你回到自己的作品 - 没有动作。如果你得到一个清楚的位 - 你可以移动到那里,并在途中捕获所有的碎片 (这种技术广泛用于许多类型的棋盘游戏,包括国际象棋)