关于语言不可知:按位运算符的真实世界用例

Real world use cases of bitwise operators

以下按位运算符的一些实际用例是什么?

  • 异或
  • 不是


  • 位字段(标志)它们是表示状态被几种"是或否"属性定义的最有效的方式。ACL是一个很好的例子;如果您假设4个离散权限(读、写、执行、更改策略),最好把它存储在1字节而不是浪费4中。为了方便起见,可以将这些映射到多种语言的枚举类型。

  • 通过端口/插座进行通信总是涉及校验和、奇偶校验、停止位、流控制算法等,这些通常取决于单个字节的逻辑值,而不是数值,因为介质可能一次只能传输一个位。

  • 压缩,加密这两种算法都严重依赖于位算法。举个例子,看看deflate算法——一切都是以位为单位的,而不是字节。

  • 有限状态机我说的主要是嵌入在某些硬件中的那种,尽管它们也可以在软件中找到。它们本质上是组合的——它们可能从字面上被"编译"成一堆逻辑门,因此它们必须表示为ANDORNOT等。

  • 绘图这里几乎没有足够的空间进入图形编程中使用这些运算符的每个区域。EDOCX1(或^)在这里特别有趣,因为第二次应用相同的输入将撤消第一次。旧的gui过去依赖于此进行选择突出显示和其他覆盖,以消除昂贵的重画需求。它们在慢速图形协议(即远程桌面)中仍然有用。

这些只是我提出的前几个例子——这并不是一个详尽的清单。


奇怪吗?

1
(value & 0x1) > 0

它能被二整除吗(偶数)?

1
(value & 0x1) == 0


低级编程就是一个很好的例子。例如,您可能需要向内存映射寄存器写一个特定的位,以使某些硬件做您想做的事情:

1
2
3
4
5
6
7
8
9
volatile uint32_t *register = (volatile uint32_t *)0x87000000;
uint32_t          value;
uint32_t          set_bit   = 0x00010000;
uint32_t          clear_bit = 0x00001000;

value = *register;            // get current value from the register
value = value & ~clear_bit;   // clear a bit
value = value | set_bit;      // set a bit
*register = value;            // write it back to the register

此外,EDOCX1·0和EDCOX1,1〕使用EDCOX1,2,EDCX1,3个操作符(在其字节数与字节顺序不匹配的机器上)实现:

1
2
3
4
5
6
7
#define htons(a) ((((a) & 0xff00) >> 8) | \
                  (((a) & 0x00ff) << 8))

#define htonl(a) ((((a) & 0xff000000) >> 24) | \
                  (((a) & 0x00ff0000) >>  8) | \
                  (((a) & 0x0000ff00) <<  8) | \
                  (((a) & 0x000000ff) << 24))


下面是一些处理作为单个位存储的标志的常见习惯用法。

1
2
3
4
5
6
7
8
enum CDRIndicators {
  Local = 1 << 0,
  External = 1 << 1,
  CallerIDMissing = 1 << 2,
  Chargeable = 1 << 3
};

unsigned int flags = 0;

设置收费标志:

1
flags |= Chargeable;

清除CallerID缺少标志:

1
flags &= ~CallerIDMissing;

测试是否设置了CallerID Missing和Charged:

1
2
3
if((flags & (CallerIDMissing | Chargeable )) == (CallerIDMissing | Chargeable)) {

}


例如,我使用它们从打包的颜色值中获取RGB(A)值。


我使用了位操作来实现CMS的安全模型。它有页面,如果用户在适当的组中,他们可以访问这些页面。一个用户可能在多个组中,因此我们需要检查用户组和页面组之间是否存在交叉点。因此,我们为每个组分配了一个唯一的2次幂标识符,例如:

1
2
3
Group A = 1 --> 00000001
Group B = 2 --> 00000010
Group C = 3 --> 00000100

我们或者将这些值放在一起,并将值(作为单个int)存储在页面中。例如,如果A&B组可以访问页面,我们将值3(二进制为00000011)存储为页面访问控制。以同样的方式,我们用一个用户存储一个ORD组标识符的值来表示它们所在的组。

因此,要检查给定用户是否可以访问给定的页面,只需将这些值和在一起,并检查这些值是否为非零。这是非常快的,因为这个检查是在一条指令中实现的,没有循环,没有数据库往返。


当我有一堆布尔标记时,我喜欢将它们全部存储在一个int中。

我用位和把它们取出来。例如:

1
2
3
4
5
6
7
8
int flags;
if (flags & 0x10) {
  // Turn this feature on.
}

if (flags & 0x08) {
  // Turn a second feature on.
}

等。


加密是所有按位操作。


和=屏蔽特定的位。您正在定义应该显示的特定位。或不显示。0x0&AM. X将清除所有位在一个字节,而0xFF将不会改变X。0x0f将在下半字节中显示位。

转换:为了将较短的变量转换成具有比特标识的较长的变量,有必要调整位,因为int中的1是0xFFFFFFF,而-0中的1是0xFFFFFFFFFFFFFFFF。保存转换后应用的身份标识。

=或设置位。如果已经设置了位,则位将被独立设置。许多数据结构(位域)都有如下标志:is_hset=0,is_vset=1,可以独立设置。要设置标志,请应用is_hset_is_vset(在c和assembly中,这非常方便阅读)

^ =异或查找相同或不同的位。

~=不翻转位。

可以证明,所有可能的本地位操作都可以通过这些操作实现。因此,如果您愿意,可以只通过位操作实现一条加法指令。

一些很棒的黑客:

网址:http://www.ugcs.caltech.edu/~wnoise/base2.html网址:http://www.jjj.de/bitwizardry/bitwizardrypage.html


三分钟前我刚刚用位XOR(^来计算与PLC串行通信的校验和…


您可以使用它们作为一种快速而肮脏的散列数据的方法。

1
2
3
4
int a = 1230123;
int b = 1234555;
int c = 5865683;
int hash = a ^ b ^ c;

按位&;用于屏蔽/提取字节的某一部分。

1字节变量

1
2
3
4
 01110010
&00001111 Bitmask of 0x0F to find out the lower nibble
 --------
 00000010

特别是移位运算符(<>>)通常用于计算。


这是一个从字节格式的位图图像中读取颜色的示例。

1
2
3
4
5
6
7
byte imagePixel = 0xCCDDEE; /* Image in RRGGBB format R=Red, G=Green, B=Blue */

//To only have red
byte redColour = imagePixel & 0xFF0000; /*Bitmasking with AND operator */

//Now, we only want red colour
redColour = (redColour >> 24) & 0xFF;  /* This now returns a red colour between 0x00 and 0xFF.

我希望这个小小的例子有帮助…


在当今现代语言的抽象世界里,不算太多。文件IO是一个容易想到的问题,尽管它正在对已经实现的东西执行位操作,而不是实现使用位操作的东西。不过,作为一个简单的示例,此代码演示如何在C中删除文件的只读属性(以便它可以与指定filemode.create的新文件流一起使用):

1
2
3
4
5
6
7
8
9
10
11
//Hidden files posses some extra attibutes that make the FileStream throw an exception
//even with FileMode.Create (if exists -> overwrite) so delete it and don't worry about it!
if(File.Exists(targetName))
{
    FileAttributes attributes = File.GetAttributes(targetName);

    if ((attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
        File.SetAttributes(targetName, attributes & (~FileAttributes.ReadOnly));

    File.Delete(targetName);
}

对于自定义实现,下面是一个最近的示例:我创建了一个"消息中心",用于将安全消息从分布式应用程序的一个安装发送到另一个安装。基本上,它类似于电子邮件,包括收件箱、发件箱、已发送等,但它也保证了已读回执的送达,因此除了"收件箱"和"已发送"之外还有其他子文件夹。这意味着我需要大致定义"收件箱"中的内容或"已发送文件夹"中的内容。在"已发送"文件夹中,我需要知道哪些内容已读,哪些内容未读。对于未读的内容,我需要知道哪些内容已接收,哪些内容未接收。我使用这些信息构建一个动态的WHERE子句,它过滤本地数据源并显示适当的信息。

以下是枚举的组合方式:

1
2
3
4
5
6
7
8
9
10
11
    public enum MemoView :int
    {
        InboundMemos = 1,                   //     0000 0001
        InboundMemosForMyOrders = 3,        //     0000 0011
        SentMemosAll = 16,                  //     0001 0000
        SentMemosNotReceived = 48,          //     0011
        SentMemosReceivedNotRead = 80,      //     0101
        SentMemosRead = 144,                //     1001
        Outbox = 272,                       //0001 0001 0000
        OutBoxErrors = 784                  //0011 0001 0000
    }

你看到了吗?通过使用"inbox"枚举值inboundmemos(&;)进行anding,我知道inboundmemosformyorders在收件箱中。

下面是方法的一个简化版本,它构建并返回为当前选定文件夹定义视图的筛选器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
    private string GetFilterForView(MemoView view, DefaultableBoolean readOnly)
    {
        string filter = string.Empty;
        if((view & MemoView.InboundMemos) == MemoView.InboundMemos)
        {
            filter ="<inbox filter conditions>";

            if((view & MemoView.InboundMemosForMyOrders) == MemoView.InboundMemosForMyOrders)
            {
                filter +="<my memo filter conditions>";
            }
        }
        else if((view & MemoView.SentMemosAll) == MemoView.SentMemosAll)
        {
            //all sent items have originating system = to local
            filter ="<memos leaving current system>";

            if((view & MemoView.Outbox) == MemoView.Outbox)
            {
                ...
            }
            else
            {
                //sent sub folders
                filter +="";

                if((view & MemoView.SentMemosNotReceived) == MemoView.SentMemosNotReceived)
                {
                    if((view & MemoView.SentMemosReceivedNotRead) == MemoView.SentMemosReceivedNotRead)
                    {
                        filter +="<not received and not read conditions>";
                    }
                    else
                        filter +="<received and not read conditions>";
                }
            }
        }

        return filter;
    }

非常简单,但在抽象层次上的整洁实现通常不需要按位操作。


我很惊讶没有人为互联网时代选择了显而易见的答案。正在计算子网的有效网络地址。

http://www.topwebhosts.org/tools/netmask.php


base64编码就是一个例子。base64编码用于将二进制数据表示为可打印字符,以便通过电子邮件系统(以及其他用途)发送。base64编码将一系列8位字节转换为6位字符查找索引。位操作、移位和"ing"或"ing,not"对于实现base64编码和解码所需的位操作非常有用。

当然,这只是无数例子中的一个。


位运算符对于循环长度为2次方的数组很有用。正如许多人所提到的,位运算符非常有用,用于标记、图形、网络和加密。不仅如此,而且速度非常快。我个人最喜欢的用法是循环一个没有条件的数组。假设您有一个基于零索引的数组(例如,第一个元素的索引是0),并且需要无限循环它。无限期地,我的意思是从第一个元素到最后一个元素再回到第一个元素。实现这一点的一种方法是:

1
2
3
4
5
6
7
8
int[] arr = new int[8];
int i = 0;
while (true) {
    print(arr[i]);
    i = i + 1;
    if (i >= arr.length)
        i = 0;
}

这是最简单的方法,如果你想避免if语句,你可以使用模数方法,比如:

1
2
3
4
5
6
7
int[] arr = new int[8];
int i = 0;
while (true) {
    print(arr[i]);
    i = i + 1;
    i = i % arr.length;
}

这两种方法的缺点是模算子是昂贵的,因为它寻找整数除法后的余数。第一种方法在每次迭代中运行if语句。然而,按位运算符,如果你的数组长度是2的幂,那么你可以很容易地使用EDCOX1×7(位和)运算符生成一个序列,比如EDCOX1×6。所以知道了这一点,上面的代码就变成了

1
2
3
4
5
6
7
int[] arr = new int[8];
int i = 0;
while (true){
    print(arr[i]);
    i = i + 1;
    i = i & (arr.length - 1);
}

这就是它的工作原理。在二进制格式中,每一个2的幂被1减去的数字只能用1来表示。例如3在二进制中是11,7是111,15是1111等等,你就知道了。现在,如果你用cx1〔7〕对一个只有一个二进制数的数求反,会发生什么?假设我们这样做:

1
num & 7;

如果num小于或等于7,则结果将为num,因为每个带1的&位本身就是。如果num大于7,在&操作期间,计算机将考虑7的前导零,当然,在&操作后,这些前导零将保留为零,只有后半部分将保留。就像在9 & 7的二进制文件中一样

1
1001 & 0111

结果将是0.001,它是十进制的1,并且在数组中寻址第二个元素。


似乎没有人提到定点数学。

(是的,我老了,好吗?)


通常,位运算比乘法/除法快。所以如果你需要用变量x乘以9,你会得到x<<3 + x,它比x*9快几个周期。如果此代码在ISR中,您将节省响应时间。

类似地,如果您希望将数组用作循环队列,那么使用逐位操作处理环绕检查会更快(也更优雅)。(数组大小应为2的幂)。例如:如果要插入/删除,可以使用tail = ((tail & MASK) + 1)而不是tail = ((tail +1) < size) ? tail+1 : 0

另外,如果您希望一个错误标志将多个错误代码放在一起,则每个位可以保存一个单独的值。您可以将它与每个单独的错误代码作为检查。这在Unix错误代码中使用。

另外,一个n位位图可以是一个非常酷和紧凑的数据结构。如果要分配大小N的资源池,可以使用n位表示当前状态。


如果你想计算你的数字mod(%)的某个2的幂,你可以使用yourNumber & 2^N-1,在这种情况下,它与yourNumber % 2^N相同。

1
2
number % 16 = number & 15;
number % 128 = number & 127;

这可能只是一个有用的替代模量运算非常大的股息是2^n…但即使这样,在我对.NET 2.0的测试中,它在模数运算上的速度提升也可以忽略不计。我怀疑现代编译器已经执行了这样的优化。有人知道更多吗?


我用它们来选择多个选项,这样我只存储一个值而不是10个或更多。


它在SQL关系模型中也很方便,假设您有以下表:blogentry、blogcategory

传统上,您可以使用BlogentryCategory表在它们之间创建N-N关系。或者,当没有太多BlogCategory记录时,可以使用BlogEntry中的一个值链接到多个BlogCategory记录,就像处理标记的枚举一样,在大多数RDBMS中,在"标记"列上也有一个非常快速的运算符来选择…


当您只想更改微控制器输出的一些位,但要写入的寄存器是一个字节时,您可以这样做(伪代码):

1
2
3
char newOut = OutRegister & 0b00011111 //clear 3 msb's
newOut = newOut | 0b10100000 //write '101' to the 3 msb's
OutRegister = newOut //Update Outputs

当然,许多微控制器允许你单独改变每一位…


数字x是2的幂吗?(例如,在计数器递增的算法中很有用,并且只采取对数次数的操作)

1
(x & (x - 1)) == 0

整数x的最高位是什么?(例如,这可用于查找大于x的2的最小功率)

1
2
3
4
5
6
x |= (x >>  1);
x |= (x >>  2);
x |= (x >>  4);
x |= (x >>  8);
x |= (x >> 16);
return x - (x >>> 1); //">>>" is unsigned right shift

哪个是整数x的最低1位?(帮助查找可被2除尽的次数。)

1
x & -x


我见过他们在基于角色的访问控制系统中使用。


河内塔线性解采用逐位运算来解决这个问题。

1
2
3
4
5
6
7
8
9
public static void linear(char start, char temp, char end, int discs)
{
    int from,to;
    for (int i = 1; i < (1 << discs); i++) {
        from = (i & i-1) % 3;
        to = ((i | i-1) + 1) % 3;
        System.out.println(from+" =>"+to);
    }
}

这个解决方案的解释可以在这里找到


每当我第一次启动C编程时,我都会理解真值表和所有这些,但直到我阅读本文http://www.gamedev.net/reference/articles/article1563.asp(它给出了现实生活中的例子),它才真正地使用它。


它们主要用于按位运算(惊喜)。下面是一些在PHP代码库中找到的实际示例。

字符编码:

1
if (s <= 0 && (c & ~MBFL_WCSPLANE_MASK) == MBFL_WCSPLANE_KOI8R) {

数据结构:

1
ar_flags = other->ar_flags & ~SPL_ARRAY_INT_MASK;

数据库驱动程序:

1
dbh->transaction_flags &= ~(PDO_TRANS_ACCESS_MODE^PDO_TRANS_READONLY);

编译器实现:

1
opline->extended_value = (opline->extended_value & ~ZEND_FETCH_CLASS_MASK) | ZEND_FETCH_CLASS_INTERFACE;

我不认为这算是按位的,但是Ruby的数组通过普通的整数按位运算符定义了集合操作。因此,[1,2,4] & [1,2,3] # => [1,2]。同样适用于a ^ b #=> set differencea | b #=> union


在我的问题中有一个现实世界的用法-只响应第一个wm_keydown通知?

在使用WindowsC API位30中的wm_keydown消息时,指定以前的密钥状态。如果在发送消息之前键是向下的,则值为1;如果键是向上的,则值为零。


我经常使用位操作将选项组合存储在单个整数中。

1
int options = 0;

其中OPTION1可定义为1,OPTION2定义为2,OPTION3定义为4,OPTION4定义为8,OPTION5定义为16,…

void addOption(int option)将使用|运算符向选项添加选项。

boolean hasOption(int option)将使用&操作符来测试选项中的选项。


我在一些游戏开发书籍中看到它是一种更有效的乘法和除法。

1
2
2 << 3 == 2 * 8
32 >> 4 == 32 / 16


还没有人提到收藏。有时可能值的集合很小,比如说只有10或20个可能值,您希望将其中一些值保留在一个集合中。当然,您可以使用常规的Set实现,它最有可能使用支持哈希表。但由于可能值的集合太小,这实际上只是浪费时间和空间。相反,您可以将集合存储在一个EDCOX1×1或EDCOX1×2的值中,如果我没有记错的话,它就是Java EDCOX1 3所做的。


我使用它们作为选项处理程序,例如在ACCES控制列表中描述特定的资源。

看一看这篇文章HTTP://PrimoZH.COM/BLG/06/05/PHP-位操作符-用例-/

编辑:

还有一个环节:HTTP://BULG.CODE-HOAD.COM/WORK-WORD -ASPESS系统-USSION BITS和BITWISE操作-in PHP


我写了一个小wiki文章一会儿回来显示一个二进制写/读。它在位级别上工作,并显示如何使用位运算符来打包数据。这可能是一个"真实世界"的例子,因为它在游戏中有应用程序。


一个非常具体的例子,但是我用它们让我的数独求解器运行得更快(我和一个朋友竞争)

每列、行和3x3都表示为无符号整数,当我设置数字时,我会为相关列、行和3x3平方中设置的数字标记适当的位。

这就使得我很容易看到在给定的正方形中可以放置哪些可能的数字,因为我会或将右边的列、行和3x3平方放在一起,而不是为了给我留下一个表示给定位置可能的合法值的掩码。

希望这是合理的。


我一直假设按位操作是相当简单的操作,所以当运行时间很关键时,通过位集实现的解决方案可以通过一个常量来提高运行时间,具体取决于算法。


一种常见的用法是对齐,例如我需要在4字节或16字节边界上对齐数据。这在RISC处理器中非常常见,其中未对齐的加载/存储很昂贵(因为它触发异常处理程序,然后需要修复未对齐的加载),或者根本不允许。

对于2次幂的任何对齐,下一个对齐的位置可以计算如下:

1
aligned_offset = alignment + ((current_offset - 1) & ~(alignment - 1))

因此,在4字节对齐和当前偏移量为9的情况下,则:

1
aligned_offset = 4 + ((9-1) & ~(4-1)) = 4 + (8 & 0xFFFFFFFC) = 4+ 8  = 12

所以接下来4字节对齐的偏移量将是12


数据库世界中的另一个现实世界应用程序是mysql,它有一个名为set的数据类型。

位运算符由DBMS存储所设置的数据类型。集合可以节省空间。

1
2
3
4
5
Element    SET Value    Decimal Value
Travel      00000001    1
Sports      00000010    2
Dancing    00000100    4
Fine Dining   00001000  8

我使用它们来实现快速的BCD计算(会计师和审计员对FP舍入感到不安)。


我们使用位标志使会话变小,以获得内部网站的登录权限。


RAID 5/6!!当您与此网站交互时,您可能正在使用它!这不是真的吗??