位运算符有什么用处?[关闭]


19

编程语言通常带有各种位运算符(例如,按位左移和右移,按位AND,OR,XOR ...)。这些虽然很少使用,但至少我的经验是这样。有时将它们用于编程挑战或面试问题中,或者可能需要解决方案,例如:

  • 不使用任何相等运算符,创建一个函数,true当两个值相等时返回
  • 在不使用第三个变量的情况下,交换两个变量的值

然后这些,可能在现实世界中很少使用。我猜他们应该更快,因为它们直接在低级别上操纵内存。

为什么在大多数编程语言中都可以找到这种语言?任何现实的用例?


@Anto-一个简单的示例是,一次以256个字(4096字节)的速率向客户端发送价值256Kb的数据。
Ramhound

1
“在不使用任何相等运算符的情况下,创建一个在两个值相等时返回true的函数”-在C return !(x-y);:?我不知道
安德鲁·阿诺德

@Andrew:这是一个解决方案,但是您也可以使用按位运算符来实现。
安托

19
“这些用的不是很多” -确定吗?我一直都在使用它们。我们并非在您的问题领域中都工作。
Ed S.

2
尚不足以得到一个完整的答案,但请尝试读取一个字节的前4位而不会造成任何混乱,然后考虑将某些数据格式打包得非常紧密。
恢复莫妮卡

Answers:


53

不,它们具有许多实际应用程序,并且是计算机上的基本操作。

它们用于

  • 杂乱的字节块不适合编程语言数据类型
  • 将编码从大端到小端来回切换。
  • 将4个6位数据打包为3个字节,以进行一些串行或USB连接
  • 许多图像格式具有分配给每个颜色通道的位数不同。
  • 嵌入式应用中涉及IO引脚的任何事物
  • 数据压缩,通常没有适合8位边界的数据。\
  • 哈希算法,CRC或其他数据完整性检查。
  • 加密
  • 伪随机数生成
  • 团队5使用卷之间的按位XOR计算奇偶校验。
  • 更多吨

实际上,从逻辑上讲,计算机上的所有操作最终都归结为这些低级按位操作的组合,发生在处理器的电子门内。


1
为您的综合列表+1,您甚至似乎还添加了该列表
Anto

28
+1。@Anto:这个列表远不完整。系统编程中针对按位运算符的用例的完整列表将与业务应用程序中SQL查询的完整列表一样长。有趣的事实:我一直在使用按位运算,但是多年来没有写过SQL语句;-)
nikie 2011年

4
@nikie:而且我一直都在写SQL,但是多年来没有使用按位运算符!:)
FrustratedWithFormsDesigner

3
我在嵌入式系统中工作-按位运算符是面包和黄油。每天使用,无需任何思考。
quick_now 2011年

9
如果我偶尔在SQL中使用移位,是否可以获得奖励?
蚂蚁

13

因为它们是基本操作。

用同样的思路,您可以争辩说加法几乎没有实际用途,因为加法可以完全用减法(和负)和乘法代替。但是我们保留加法,因为这是一项基本操作。

而且暂时不要认为仅仅因为您还没有看到对按位运算的太多需求,并不意味着它们不经常使用。确实,我几乎在用于位屏蔽之类的所有语言中都使用了按位运算。

在我的头顶上,我使用了按位运算进行图像处理,位域和标志,文本处理(例如,特定类的所有字符通常共享一个公共位模式),编码和解码序列化数据,解码VM或CPU操作码,等等。如果没有按位操作,则大多数这些任务将需要许多次复杂的操作才能使该任务可靠性降低或可读性降低。

例如:

// Given a 30-bit RGB color value as a 32-bit int
// A lot of image sensors spit out 10- or 12-bit data
// and some LVDS panels have a 10- or 12-bit format
b = (color & 0x000003ff);
g = (color & 0x000ffc00) >> 10;
r = (color & 0x3ff00000) >> 20;

// Going the other way:
color = ((r << 20) & 0x3ff00000) | ((g << 10) & 0x000ffc00) | (b & 0x000003ff);

解码RISC型CPU的CPU指令(例如在仿真另一个平台时)需要如上所述提取较大值的部分。有时,使用乘法,除法和模等方法进行这些运算可能要比等效的按位运算慢10倍之多。


12

一个典型的示例是从24位RGB值中提取各个颜色并返回。


编辑:从http://www.docjar.com/html/api/java/awt/Color.java.html

    value =  ((int)(frgbvalue[2]*255 + 0.5))    |
                (((int)(frgbvalue[1]*255 + 0.5)) << 8 )  |
                (((int)(frgbvalue[0]*255 + 0.5)) << 16 ) |
                (((int)(falpha*255 + 0.5)) << 24 );

在实践中显示此示例?代码段?
安托

更好的示例可能是处理16位(4.5bpc)或30位(10bpc)RGB值。
greyfade 2011年

@灰色,请随时添加此类示例。

6

这是一个真实的示例,可以在Quake 3,Quake 4中找到。DoomIII。所有使用Q3引擎的游戏

float Q_rsqrt( float number )
{
        long i;
        float x2, y;
        const float threehalfs = 1.5F;

        x2 = number * 0.5F;
        y  = number;
        i  = * ( long * ) &y;                       // evil floating point bit level hacking [sic]
        i  = 0x5f3759df - ( i >> 1 );               // what the fuck? [sic]
        y  = * ( float * ) &i;
        y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
//    y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

        return y;
}

(要了解该代码,您需要了解浮点数是如何存储的,我绝对不能对此进行详细说明)

在使用方面,除非您处在需要位移的领域,例如网络或图形,否则您可能会发现它们的用途有些学术性。但是仍然很有趣(对我来说至少)。


+1对于这些评论,即使它们不是您的。让我笑了。
Bassinator

4

移位比乘或除以2的幂更快。例如,<< = 2乘以4。相反,>> = 2将a除以4。也可以使用按位运算符将数据按位发送到设备。例如,我们可以使用N个循环内的shift,xor和“ and”操作从N引脚端口发送N个串行数据流。在数字逻辑中可以完成的任何事情也可以在软件上完成,反之亦然。


1
在进行舍入或舍入等除法时请务必小心。移位并不能解决这个问题,因此,我发现在进行除法运算时最好在代码中使用除法,让编译器将其优化为移位并加法。为了我。

@Daemin:使用此技术时,我正在使用整数。在C和C ++中,整数除法的默认行为是截断为零。因此,将整数右移2的幂将产生与将整数除以2的幂相同的结果。
bit-twiddler 2011年

1
@ bit-twiddler右移与负数除法的工作方式不同。

@Daemin:你似乎很想证明我错了。首先,您提出了舍入问题。当我通过声明C和C ++中的除法截断为零来否认该主张时,您抛出了有符号整数问题。我在哪里说我在将shift运算符应用于带符号的补码负整数?话虽如此,仍然可以使用移位运算符除以2的幂。但是,由于C和C ++执行的是算术右移而不是普通的旧右移,因此必须首先检查值是否为负。如果该值为负,
bit-twiddler 2011年

1
的确,在使用移位代替乘法和除法时要小心,因为存在细微的差异。不多不少。

3

很久以前,位运算符很有用。如今,情况已不再如此。哦,它们并不是完全没有用的,但是已经有很长的时间了,因为我已经看到一个应该使用的用过的东西。

1977年,我是一名汇编语言程序员。我坚信汇编程序是唯一的真正语言。我确定像Pascal这样的语言是用于学术家的,他们从来不需要真正完成任何工作。

然后,我读了Kernighan和Ritchie的“ The C Programming Language”。它彻底改变了我的想法。原因?它有位运算符!这一种汇编语言!它只是语法不同。

那时,我无法想到没有AND,OR,移位和旋转就可以编写代码。如今,我几乎从未使用过它们。

因此,对您问题的简短回答是:“什么都没有。” 但这并不公平。因此,更长的答案是:“几乎没有。”



如今,位运算符非常有用。在您的域中不使用它们的事实不会使它不被使用,甚至不经常使用。这是一个简单的示例:尝试在没有位运算符的情况下实现AES。这是大多数计算机每天完成数百或数千次操作的即时示例。
我的正确观点

不使用按位运算符对数据进行编码/解码充其量是痛苦的。例如,在邮件中添加MIME附件要求我们能够处理三到四个数据编码(也称为radix64编码)。
bit-twiddler 2011年

2

加密

我建议看一下DES加密算法中的一个非常小的片段:

temp = ((left >>> 1) ^ right) & 0x55555555; right ^= temp; left ^= (temp << 1);
temp = ((right >>> 8) ^ left) & 0x00ff00ff; left ^= temp; right ^= (temp << 8);
temp = ((right >>> 2) ^ left) & 0x33333333; left ^= temp; right ^= (temp << 2);
temp = ((left >>> 16) ^ right) & 0x0000ffff; right ^= temp; left ^= (temp << 16);
temp = ((left >>> 4) ^ right) & 0x0f0f0f0f; right ^= temp; left ^= (temp << 4);

尽管不确定现在是否建议使用DES:P
Armand

@Alison:不,但是我认为取代它的加密算法涉及更多的位操作。:-)
Carson63000 2011年

@Alison-当然,但是TripleDES只是DES完成了3次,密钥位是3倍。
Scott Whitlock

2

很多好的答案,所以我不再重复这些用法。

我在托管代码(C#/ .Net)中经常使用它们,与节省空间,高性能或聪明的移位算法无关。有时某些逻辑非常适合以这种方式存储数据。当我有一个枚举时,我经常使用它们,但是实例可以同时从该枚举中获取多个值。我无法从工作中发布代码示例,但是针对“ Flags枚举”(“ Flags”是C#定义按位方式使用的枚举的C#方法)的快速google给出了一个很好的示例:http:// www.dotnetperls.com/enum-flags


2

还有位并行计算。如果您的数据只有1和0,则可以将其中的64个打包成一个无符号的长长字,并进行64路并行操作。遗传信息只有两位(代表DNA的AGCT编码),如果您可以位并行的方式进行各种计算,则可以做很多。更不用说内存中数据的密度-如果内存,磁盘容量或通信带宽受到限制,则意味着应考虑压缩/解压缩。甚至在图像处理等领域中出现的低精度整数也可以利用棘手的位并行计算。它本身就是一门完整的艺术。


1

为什么找到它们?

嗯,这可能是因为它们与汇编指令相对应,有时它们仅对高级语言有用。相同的内容适用GOTO于与JMP汇编指令相对应的恐惧。

它们有什么用?

确实有很多用途可以命名,所以我只给出最近的(尽管高度本地化的)用法。我在处理6502程序集方面做了大量工作,并且正在开发一个小型应用程序,该程序将内存地址,值,比较值等转换为可用于GameGenie设备的代码(基本上是NES的作弊应用程序)。这些代码是通过一些位操作创建的。


1

如今,许多程序员已经习惯了具有接近无限内存的计算机。

但是,有些仍在使用的微型微控制器会编程每个位都很重要的微控制器(例如,当您只有1k或更少的RAM时),按位运算符允许程序员一次使用这些位,而不会浪费大量的编程时间抽象实体可能不具备保持算法所需的某些状态。这些设备上的IO也可能需要按位读取或控制。

“现实世界”拥有的微型微控制器远远超过服务器或PC。

对于纯理论CS类型,图灵机都是关于状态位的。


1

按位运算符的许多可能用法中的仅另一种...

按位运算符还可以帮助提高代码的可读性。考虑以下函数声明...。

int  myFunc (bool, bool, bool, bool, bool, bool, bool, bool);

...

myFunc (false, true, false, false, false, true, true, false);

很容易忘记在编写甚至读取代码时哪个布尔参数意味着什么。也很容易忘记您的计数。这样的例程可以清除。

/* More descriptive names than MY_FLAGx would be better */
#define MY_FLAG1    0x0001
#define MY_FLAG2    0x0002
#define MY_FLAG3    0x0004
#define MY_FLAG4    0x0008
#define MY_FLAG5    0x0010
#define MY_FLAG6    0x0020
#define MY_FLAG7    0x0040
#define MY_FLAG8    0x0080

int  myFunc (unsigned myFlags);

...

myFunc (MY_FLAG2 | MY_FLAG6 | MY_FLAG7);

使用更具描述性的标志名,它变得更具可读性。


1

如果您对Unicode有所了解,则可能熟悉UTF-8。它使用一堆位测试,移位和掩码将20位代码点打包为1至4个字节。


0

我不经常使用它们,但有时它们会派上用场。枚举处理浮现在脑海。

例:

enum DrawBorder{None = 0, Left = 1, Top = 2, Right = 4, Bottom = 8}

DrawBorder drawBorder = DrawBorder.Left | DrawBorder.Right;//Draw right & left border
if(drawBorder & DrawBorder.Left == DrawBorder.Left)
  //Draw the left border
if(drawBorder & DrawBorder.Top == DrawBorder.Top)
  //Draw the top border
//...

0

不确定是否已记录此用法:

在使用illumos(openSolaris)源代码将多个返回值减少为0或1时,我看到很多类似的信息,例如

int ret = 0;
ret |= some_function(arg, &arg2); // returns 0 or 1
ret |= some_other_function(arg, &arg2); // returns 0 or 1
return ret;
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.