逐位运算的实际应用


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

Answers:


83

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


<<>>运营商可以迅速地乘的2场的力量,.NET JIT优化可能会为你做这个(和其他语言以及任何像样的编译器),但如果你每微秒真的心乱了,你也许可以写这个来确定。

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

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

直接与Win32函数进行接口时很常见,有时出于传统原因有时会使用此技巧。

而且,当然,这些运算符在您想使经验不足的人困惑时很有用,例如在提供对作业问题的答案时。:)

但是,在任何实际代码中,您都可以通过使用乘法来获得更好的效果,因为它具有更好的可读性,并且JIT无论如何都会对其进行优化shl和执行shr指令,因此不会降低性能。


^运算符(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;
}

要从头开始遍历,只需将第一行从更改FirstItemLastItem。那是在那里节省的另一个内存。

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

13
+1,漂亮的xor交换。谢谢。
Grozz 2010年

4
+1的xor-list方法以前从未见过。
2010年

2
有趣的是,这被标记为非建设性的:)
亚历克斯·戈登

“当您想使经验不足的人感到困惑时” +1为MO指明了太多高级开发人员/建筑师的方式。我真的以为经验会带来谦虚的实用主义,可惜没有。布罗格默斯已经接管了,世界为此变得更糟。
达沃斯2015年

+1000 +“尽管在任何实际代码中,您都可以使用乘法来改善性能,因为它具有更好的可读性,并且JIT始终将其优化为shl和shr指令,因此不会降低性能。” 为什么代码高尔夫在真实代码中如此流行?
达沃斯2015年

74

我在应用程序中使用按位运算符来确保安全性。我将在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
}

1
@justin,非常感谢。您能否解释一下此User.Permissions = SecurityLevel.SuperUser | SecurityLevel.AnswerAdmin;
Alex Gordon

这些只是标记的枚举。
戴夫

@jenny-在代码注释中添加了说明。
Justin Niessner

1
@Dave-是的。但是,当使用标记枚举时,您将使用按位运算符对值进行检查。方便且相当简单。我认为,这正是OP的要求。
Justin Niessner

7
@Justin:尽管已过时,但Enum.HasFlag:msdn.microsoft.com/en-us/library/system.enum.hasflag.aspx
Reed Copsey 2010年

16

我不知道如何解决您认为的数独问题,但让我们假设它是可行的。

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

电路板本身很可能由二维数组表示,例如:

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

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

现在想象一下,您想检查一下是否合法。显然,您可以通过几个循环来完成此操作,但是位掩码使您可以更快地完成操作。在一个仅能确保遵守规则的简单程序中,这并不重要,但在求解程序中则可以。

您可以维护位掩码数组,这些掩码存储有关已插入到每一行,每一列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);

在上面1u对应于位模式的行中00000000 00000000 00000000 00000001左移value - 1。例如value == 5,如果

00000000 00000000 00000000 00010000

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

假设您在第3行中插入值5(行和列从0开始编号)。第3行的遮罩存储在中maskForNumbersSetInRow[3]。我们还假设在插入之前{1, 2, 4, 7, 9}第3行中已经有数字。掩码中的位模式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

现在,您的掩码指示{1, 2, 4, 5, 7, 9}该行(第3行)中有值。

如果要检查,如果行中有一些值,则可以使用 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
真的很有趣,但是有点让我震惊。似乎可能是按顺序进行了一些构建(一些注释和示例)。构建面罩的位偏移对我来说有点令人困惑。只是问一下,因为您明确地花了一些时间回答。
标记


3

如果您需要与硬件进行通信,则有时需要使用位旋转。

提取像素值的RGB值。

这么多的事情


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

糟糕,我刚才看到您标记了此C#,我以为是C ++。...阅读速度较慢... :)
C约翰逊

2

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

Java中的按位与,按位包含或问题

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

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

此处的MSN文章->

http://msdn.microsoft.com/zh-CN/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

是非常技术性的,它涵盖了所有内容。

高温超导


2

只要您选择一个或多个项目组合,则按位通常很容易解决。

一些示例包括安全性位(等待贾斯汀的样本..),安排工作日等。



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但是我仅是说这通常是一个坏主意,因为它隐藏了真实代码的意图,几乎没有节省任何性能,并且可以隐藏一些细微的错误。 。


1
从技术上讲,移位运算符不能视为按位运算符。请查看Wikipedia文章的移位部分,以了解原因:en.wikipedia.org/wiki/Bitwise_operation
Justin Niessner,2010年

1

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


1

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

  • 以内存有效的方式存储(并检查!)选项标志
  • 如果进行计算编程,则可能要考虑使用按位运算而不是数学运算符来优化某些运算(请注意副作用)
  • 格雷码
  • 创建枚举值

我相信您会想到其他人。

话虽如此,有时您需要问自己:内存和性能的提升是否值得付出努力。编写完这类代码后,让它休息一会儿再回到上面。如果您遇到困难,请使用更易于维护的代码进行重写。

另一方面,有时使用按位运算确实很有意义(请考虑使用密码术)。

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


1

游戏!

过去,我用它来代表Reversi玩家的作品。它是8X8,所以花了我一个字long,比方说,例如,如果您想知道所有棋子在哪里-你们or两个都是棋子。
如果您想让一个玩家采取所有可能的步骤,请在右边说-您>>将玩家的筹码表示为1,并AND与对手的筹码进行比较,以检查现在是否存在共同的1(即您的右边有一个对手筹码)。然后,您继续这样做。如果您恢复原状,那就别动。如果您清楚一点-可以移动到那里,并捕获途中的所有碎片。
(此技术广泛用于包括棋类在内的许多棋盘游戏中)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.