现实世界中按位运算符的用例[关闭]


224

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

  • 异或
  • 要么
  • 左/右移位

1
我记得当我第一次了解它们时,似乎只将它们用于低级c ...但是从那时起,它们已由工具箱输入,我经常使用它们,包括在进行“高级”编程时。对于我来说,它们就像+和*。
橡树

2
@Anon:在我看来,现实世界应该意味着除底层编程之外的任何东西,这是按位运算符最明显的用途。
奥利维尔·拉隆德



也可以使用二进制XOR在排列中查找缺失的数字:martinkysel.com/codility-permmissingelem-solution
Janac Meena,

Answers:


216
  • 位字段(标志)
    它们是表示某些状态由若干“是或否”属性定义的内容的最有效方法。ACL是一个很好的例子;如果您假设有4个离散权限(读,写,执行,更改策略),则最好将其存储在1个字节中,而不是浪费4个。可以将它们映射为多种语言的枚举类型,以提高便利性。

  • 通过端口/套接字进行的通信
    始终涉及校验和,奇偶校验,停止位,流控制算法等,这通常取决于单个字节的逻辑值,而不是数字值,因为介质可能仅能够在1比特处传输一位。一个时间。

  • 压缩,加密
    这两者都严重依赖于按位算法。以deflate算法为例-一切都以位为单位,而不是以字节为单位。

  • 有限状态机
    我主要说的是嵌入某些硬件中的那种状态机,尽管它们也可以在软件中找到。这些组合在本质上-他们可能从字面上可以得到“编译”到一堆逻辑门,所以他们必须表现为ANDORNOT,等。

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

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


嗨,@ Aaronaught,您真的与我们分享了非常好的知识。我有兴趣进一步了解Bitwise Operator的实际案例。您介意与我们分享您的参考资料,对进一步阅读它很有帮助。
Heena Hussain '02

按位运算对矢量化计算有用吗?
亚伦弗兰克

47

奇怪吗?

(value & 0x1) > 0

它可以被二(偶数)整除吗?

(value & 0x1) == 0

3
根据您的语言,值&0x1> 0可能被解析为值&(0x1> 0)
leeeroy 2010年

3
@leeeroy-足够真实。添加了一些括号。
赛斯

现代优化器会自动将(value%2)!= 0之类的表达式转换为上述表达式。godbolt.org/z/mYEBH4
法拉利

26

这是一些处理作为个别位存储的标志的常见用法。

enum CDRIndicators {
  Local = 1 << 0,
  External = 1 << 1,
  CallerIDMissing = 1 << 2,
  Chargeable = 1 << 3
};

unsigned int flags = 0;

设置计费标志:

flags |= Chargeable;

清除CallerIDMissing标志:

flags &= ~CallerIDMissing;

测试是否设置了CallerIDMissing和Chargeable:

if((flags & (CallerIDMissing | Chargeable )) == (CallerIDMissing | Chargeable)) {

}

25

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

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

我们将这些值或在一起,然后将值(作为单个int)存储在页面中。例如,如果A和B组可以访问页面,则我们将值3(二进制为00000011)存储为页面访问控制。以几乎相同的方式,我们与用户存储一个ORed组标识符的值,以表示它们所在的组。

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


24

低级编程是一个很好的例子。例如,您可能需要向存储器映射的寄存器写入特定的位,以使某些硬件执行您想要的操作:

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

此外,htonl()htons()是使用&|运算符实现的(在字节序(字节顺序)与网络顺序不匹配的机器上):

#define htons(a) ((((a) & 0xff00) >> 8) | \
                  (((a) & 0x00ff) << 8))

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

7
并不是每个人都在机器上讲话。您的第二个示例在做什么?
ChaosPandion 2010年

7
htons()htonl()POSIX函数可以将主机()字节序中的a short或a 交换为网络()字节顺序。longhn
卡尔·诺鲁姆

您可以使用类似的方法在O(logN)操作中进行位反转。
Mike DeSimone 2010年

大声笑,当我第一次看到这个,我认为这是组装!
0x499602D2

不是htonl()32位int值吗?long表示多种语言中的64位。
亚伦·弗兰克

21

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


而且它真的很快!
卡勒姆·罗杰斯

在C#中,对于可读性和速度而言,它实际上是最好的解决方案之一。
CaptainCasey 2010年

4
在我的机器上,(a & b) >> c速度比a % d / e(从表示ARGB的int提取单一颜色值的​​两种方法)快5倍以上。十亿次迭代分别为6.7s和35.2s。
Benji XVI 2010年

@BenjiXVI C#确实对此进行了编译器优化。观察到速度差的原因是,在C#中,%它不是Modulus运算符,而是Remainder运算符。它们等于正值,但与负值不同。如果提供了适当的限制(例如传递a uint代替int),则两个示例的速度应该相同。
亚伦弗兰克

对不起,我知道已经很久了。您能举个例子说明如何使用它们来获取每个RGB值吗?
Jinx

14

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

我使用逐位与将它们取出。例如:

int flags;
if (flags & 0x10) {
  // Turn this feature on.
}

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

等等


23
希望这些实际上是您的真实代码中的常量,而不是幻数:)
Earlz 2010年

1
在非低级环境中使用布尔标志的一个示例是使用各种GUI平台进行操作。例如,您可以使用my_button.Style | = STYLE_DISABLED将其关闭。
MauriceL 2010年

1
我知道这个主题与语言无关,但是C提供了一种使用位域进行此操作的简便方法,因此您可以使用if (flags.feature_one_is_one) { // turn on feature }。它符合ANSI C标准,因此可移植性不成问题。
polandeer 2014年

很好地解释了这段代码的作用,为什么未初始化标志,“将它们全部存储在int中”的含义,使用的表示法是什么?
Angelo Oparah

12

&= AND:
屏蔽特定的位。
您正在定义应显示或不显示的特定位。0x0和x将清除字节中的所有位,而0xFF不会更改x。0x0F将显示低半字节中的位。

转换:
若要将较短的变量转换为具有位标识的较长变量,则必须调整这些位,因为int中的-1为0xFFFFFFFF,而long中的-1为0xFFFFFFFFFFFFFFFF。要保留身份,请在转换后应用掩码。

| = OR
设置位。如果已设置这些位,则将对其进行独立设置。许多数据结构(位域)具有可以独立设置的标志,例如IS_HSET = 0,IS_VSET = 1。要设置标志,请应用IS_HSET |。IS_VSET(在C和汇编语言中,这很方便阅读)

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

〜=不
翻转位。

可以证明,所有可能的本地位操作都可以通过这些操作来实现。因此,如果您愿意,可以仅通过位操作来实现ADD指令。

一些很棒的技巧:

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


以下链接为使用AS3中的按位运算符进行超快速数学运算提供了出色的示例(但它很可能适用于大多数语言):lab.polygonal.de/2007/05/10/bitwise-gems-fast-整数
大量涉及2010年

我认为“ NOT”应为= ~,而不是|=OR。
Mike DeSimone 2010年

对于& = AND-为什么要清除所有位,为什么要获得字节的未修改版本,低位半字节该怎么办?
confused00

1
@ confused00对,将结果无效的更快/更简单的方法是xor它本身。我可以想出很多原因来提取较低的半字节。尤其是如果较低的半字节是数据结构的一部分,并且您想将其用作掩码或OR与其他结构一起使用。
James M. Lay

11

加密是所有按位操作。


4
真?加密实现可能使用按位运算,但是加密算法通常以数字形式而不是以比特表示形式描述。
君士坦丁2010年

1
那么,除了实现它们之外,您还使用其他算法做什么?我很好奇。
递归

2
@Constantin:见,例如,DES是如何实现的说明:(en.wikipedia.org/wiki/...
韦恩康拉德

1
@recursive,如果您个人询问我-我既不设计密码算法也不实施它们。但是人们做了很多事情,比如分析理论上的弱点。
君士坦丁2010年

@Constantin:看看这一点,这只是(通常)如何描述(部分)加密算法的一个例子: en.wikipedia.org/wiki/Substitution_box
SyntaxT3rr0r 2010年

9

您可以将它们用作对数据进行哈希处理的快速而肮脏的方法。

int a = 1230123;
int b = 1234555;
int c = 5865683;
int hash = a ^ b ^ c;

8

^大约三分钟前,我只是使用按位XOR()计算与PLC进行串行通信的校验和...


7

按位&用于屏蔽/提取字节的特定部分。

1字节变量

 01110010
&00001111 Bitmask of 0x0F to find out the lower nibble
 --------
 00000010

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


6

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

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.

我希望这些小例子对您有所帮助。


5

在当今现代语言的抽象世界中,没有太多。文件IO是一个容易想到的东西,尽管它对已实现的对象执行按位操作,而不是对使用按位操作的对象进行按位操作。仍然,作为一个简单的示例,此代码演示了如何在c#中删除文件上的只读属性(以便可以与指定FileMode.Create的新FileStream一起使用):

//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子句,该子句过滤本地数据源并显示适当的信息。

这是枚举的组合方式:

    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
    }

你知道这是做什么的吗?通过与(&)与“收件箱”枚举值InboundMemos进行和,我知道InboundMemosForMyOrders在收件箱中。

这是该方法的简化版本,该方法生成并返回定义当前选定文件夹视图的过滤器:

    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 += "<all sent items>";

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

非常简单,但是在抽象级别上却很整洁,通常不需要按位操作。


4

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

当然,这只是无数示例中的1个。



4

通常,按位运算比执行乘法/除法更快。因此,如果您需要将变量x乘以9,那么您会x<<3 + x比快几个周期x*9。如果此代码位于ISR中,则可以节省响应时间。

同样,如果要将数组用作循环队列,则使用按位操作来处理环绕检查会更快(更优雅)。(您的数组大小应为2的幂)。例如:如果要插入/删除,可以使用tail = ((tail & MASK) + 1)代替tail = ((tail +1) < size) ? tail+1 : 0

同样,如果您希望错误标志将多个错误代码保存在一起,则每个位可以保存一个单独的值。您可以将其与每个单独的错误代码进行“与”检查。在Unix错误代码中使用。

同样,n位位图可以是一个非常酷而紧凑的数据结构。如果要分配大小为n的资源池,我们可以使用n位来表示当前状态。



3

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

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

整数的最高位是哪一位x?(例如,可用于查找大于的2的最小幂x

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

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

x & -x

通过将LHS强制转换为无符号类型,可以在C中完成无符号右移。您的最后一个公式没有找到最低的[set]位,而是检查X是否为2的幂。要找到最低的set位,请执行do x & -x
Potatoswatter 2010年

嗯,您是对的,我以某种方式用第一个代码段替换了x&-x,thx用于编辑
Dimitris Andreou 2010年

3

按位运算符对于循环长度为2的幂的数组很有用。正如许多人提到的那样,按位运算符非常有用,并用于FlagsGraphicsNetworkingEncryption。不仅如此,而且速度也非常快。我个人最喜欢的用法是循环一个没有条件数组。假设您有一个基于零索引的数组(例如,第一个元素的索引为0),并且需要无限循环。无限期地,我的意思是从第一个元素到最后一个元素,然后返回到第一个元素。一种实现方法是:

int[] arr = new int[8];
int i = 0;
while (true) {
    print(arr[i]);
    i = i + 1;
    if (i >= arr.length) 
        i = 0;
}

这是最简单的方法,如果要避免使用if语句,则可以使用模数方法,如下所示:

int[] arr = new int[8];
int i = 0;
while (true) {
    print(arr[i]);
    i = i + 1;
    i = i % arr.length;
}

这两种方法的缺点是模量运算符很昂贵,因为它会在整数除法后寻找余数。和第一种方法运行的,如果在每次迭代语句。但是,使用按位运算符,如果数组的长度是2的幂,则可以像这样0 .. length - 1使用&(按位和)运算符轻松生成序列i & length。因此,知道了这一点,上面的代码就变成了

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等等,您就会明白。现在,如果将&任何数字与仅由二进制数字组成的数字进行比较会发生什么?假设我们这样做:

num & 7;

如果num小于或等于7,则结果将是num因为&用1填充的每个位本身就是它。如果num大于7,则在&运行过程中,计算机将考虑7的前导零,在&运行后,这些零当然会保留为零,仅尾部保留。就像在9 & 7二进制的情况下一样

1001 & 0111

结果将是0001(十进制为1)并寻址数组中的第二个元素。


如果您的数组长度是2的幂,则在文本的一半处应放在第一句。使用此技巧是一个非常严重的限制。就我个人而言,无论如何我都不会实现,与ifmod方法相比,代码更难理解。
Jan Doggen '16

@JanDoggen是的,我将其放在第一句话中。至于数组是2的幂,以我的经验,它工作的次数超过了几次。可能是因为它与网络和图形有关。
3552161

1
这篇文章的目的是说明按位运算符对于生成数字序列非常有用,0、1、2、3、0、1、2 ...序列只是我想到的第一个序列。
user3552161

2

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


2

它在sql关系模型中也可以派上用场,假设您有下表:BlogEntry,BlogCategory

传统上,您可以使用BlogEntryCategory表在它们之间创建nn关系,或者当没有太多BlogCategory记录时,可以像使用带标记枚举一样使用BlogEntry中的一个值链接到多个BlogCategory记录,在大多数RDBMS中也有一个非常快速的运算符可以在“标记”列中进行选择...


2

当您只想更改微控制器输出的某些位,而要写入的寄存器是一个字节时,您可以执行以下操作(伪代码):

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

当然,许多微控制器允许您分别更改每个位。


这应该是什么语言?
卡森·迈尔斯

@Carson:没有语言,这是伪代码。当我几年前真正完成此操作时,是Assembly,但我想它很容易用C语言完成。感谢大家的注意,我将进行更新以使其更清楚
Emilio M Bumachar 2010年

我编辑了答案以更改评论,因此突出显示的内容没有那么模糊。而我看到的,我想这可能是C,但您使用0B ...符号,我希望在C.可用
卡森·迈尔斯

2

如果您想以一定的2的幂来计算数量mod(%),则可以使用yourNumber & 2^N-1,在这种情况下,它与相同yourNumber % 2^N

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

这可能仅是对模数运算的一种替代方法,因为它的模数运算非常大,为2 ^ N ...但是即使如此,在我对.NET 2.0的测试中,它对模数运算的速度提升还是可以忽略的。我怀疑现代编译器已经在执行这样的优化。有人对此有更多了解吗?


编译器确实使用了这种优化...但是,如果您不了解该优化,则可能无法选择精确为2的除数。
本·福格特

这取决于实际情况。在C#中,这实际上会产生不同的结果,就像%余数运算一样,它们对负数的处理也不同。但是,如果传递uint%,则当第二个参数为2的已知幂时,C#编译器实际上将使用按位AND生成机器代码。
亚伦弗兰克



1

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

字符编码:

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

数据结构:

ar_flags = other->ar_flags & ~SPL_ARRAY_INT_MASK;

数据库驱动程序:

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

编译器实现:

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

1

每当我第一次开始C编程时,我都了解真值表以及所有这些内容,但是直到我阅读本文http://www.gamedev.net/reference/articles/article1563.asp时,它并没有全部点击一下如何使用它(给出了现实生活中的例子)


1
不,不一样。在C中,如果x == 1y == 2,则x || y得出1,然后x | y得出0。我也看不出为什么x^true!x任何方式都优于。它输入更多,习惯用法更少,如果x碰巧不是,bool那是不可靠的。
David Thornley,2010年

哦,等等..是的,这对我来说是愚蠢的..我今天似乎无法直截了当。
Earlz 2010年

x | y的计算为3(编辑:NVM,我看到你reffering到一些东西编辑的!)
波德

1
@DavidThornley:当一种情况x^true是优于!xsome->complicated().member->lookup ^= true; 还有一元运算符的无化合物指派的版本。
Ben Voigt

1

我认为这不算按位,但是ruby的Array通过普通的整数按位运算符定义了集合操作。所以[1,2,4] & [1,2,3] # => [1,2]a ^ b #=> set difference和和类似a | b #=> union


1

塔河内线性解决方案使用按位运算来解决问题。

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

该解决方案的说明可以在这里找到

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.