位移和算术运算有什么区别?

时间:2010-06-07 07:30:54

标签: c++ c objective-c bit-manipulation

int aNumber;

aNumber = aValue / 2;
aNumber = aValue >> 1;

aNumber = aValue * 2;
aNumber = aValue << 1;

aNumber = aValue / 4;
aNumber = aValue >> 2;

aNumber = aValue * 8;
aNumber = aValue << 3;

// etc.

什么是“最佳”的运营方式?何时更好地使用位移?

10 个答案:

答案 0 :(得分:18)

如果你使用的是正整数,那么这两个在你给出的例子中是功能相同的(除了最后一个,应该是aValue * 8 == aValue << 3)。只有在乘以或除以2的幂时才

比特移位从不比算术慢。根据您的编译器,算术版本可能会被编译为位移版本,在这种情况下,它们都是有效的。否则,位移应该比算术快得多。

然而,算术版本通常更具可读性。因此,我几乎在所有情况下都使用算术版本,并且只有在分析显示该语句存在瓶颈时才使用位移:

  

程序应该写给人们阅读,并且只是偶然让机器执行。

答案 1 :(得分:8)

不同之处在于算术运算已经明确定义了结果(除非它们遇到有符号溢出)。在许多情况下,班次操作没有定义结果。它们在C和C ++中明确定义为无符号类型,但对于带符号类型,事情很快就会变得棘手。

在C ++语言中,未定义带符号类型的左移<<的算术意义。它只是移位,在右边填充零。算术意义上的含义取决于平台使用的签名表示。对于右移>>运算符,实际上也是如此。右移负值会导致实现定义的结果。

在C语言中,事物的定义略有不同。左移负值是不可能的:它会导致不确定的行为。右移负值会导致实现定义的结果。

在大多数实际实施中,每个单独的右移执行除以2,向负无穷大舍入。这个BTW明显不同于算术除法/乘以2,因为它通常(并且始终在C99中)的时间将向0舍入。

至于何时应使用位移...位移位用于对位进行操作的操作。位移运算符很少用作算术运算符的替代(例如,你永远不应该使用移位来执行乘法/除法)。

答案 2 :(得分:4)

位移是一种“接近金属”的操作,大部分时间都不包含任何关于你真正想要实现的信息。

如果您想将数字除以2,请务必写下x/2。它恰好由x >> 1实现,但后者隐藏了意图。

如果结果成为瓶颈,请修改代码。

答案 3 :(得分:2)

什么是“最佳”的操作方式?

处理数字时使用算术运算。处理位时使用位操作。期。这是常识。我怀疑是否有人会认为使用比特移位操作进行整数或双击作为常规日常事物是一个好主意。

何时更好地使用位移?

处理位时?

其他问题:算术溢出时它们的行为是否相同?

是。大多数现代编译器都会(通常但并非总是)将适当的算术运算简化为其位移对应物。

编辑:接受了答案,但我只想补充一点,在这个问题上有很多不好的建议。在处理整数时,你永远不应该(读:几乎从不)使用位移操作。这是一种可怕的做法。

答案 4 :(得分:2)

当你的目标是增加一些数字时,使用算术运算符是有意义的。

当您的目标是实际逻辑上移位时,请使用移位运算符。

例如,假设您要从RGB字中分割RGB分量,这段代码是有道理的:

 int r,g,b;
 short rgb = 0x74f5;
 b = rgb & 0x001f;
 g = (rgb & 0x07e0) >> 5;
 r = (rgb & 0xf800) >> 11;

另一方面,当你想要将某个值乘以4时,你应该真正编码你的意图,而不是做换班。

答案 5 :(得分:1)

只要您在2er功率范围内相乘或分割,就可以更快地进行换档操作,因为它只需一个操作(只需要一个过程循环)。
一个习惯于阅读&lt;&lt; 1作为* 2和&gt;&gt; 2作为/ 4非常快,所以我不同意使用移位时可读性消失但这取决于每个人。

如果您想了解更多关于如何以及为何的详细信息,也许维基百科可以提供帮助,或者如果您想要通过痛苦学习装配; - )

答案 6 :(得分:1)

作为差异的示例,这是使用gcc 4.4和-O3

创建的x86程序集
int arithmetic0 ( int aValue )
{
    return aValue / 2;
}

00000000 <arithmetic0>:
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   8b 45 08                mov    0x8(%ebp),%eax
   6:   5d                      pop    %ebp
   7:   89 c2                   mov    %eax,%edx
   9:   c1 ea 1f                shr    $0x1f,%edx
   c:   8d 04 02                lea    (%edx,%eax,1),%eax
   f:   d1 f8                   sar    %eax
  11:   c3                      ret    

int arithmetic1 ( int aValue )
{
    return aValue >> 1;
}

00000020 <arithmetic1>:
  20:   55                      push   %ebp
  21:   89 e5                   mov    %esp,%ebp
  23:   8b 45 08                mov    0x8(%ebp),%eax
  26:   5d                      pop    %ebp
  27:   d1 f8                   sar    %eax
  29:   c3                      ret    

int arithmetic2 ( int aValue )
{
    return aValue * 2;
}

00000030 <arithmetic2>:
  30:   55                      push   %ebp
  31:   89 e5                   mov    %esp,%ebp
  33:   8b 45 08                mov    0x8(%ebp),%eax
  36:   5d                      pop    %ebp
  37:   01 c0                   add    %eax,%eax
  39:   c3                      ret    

int arithmetic3 ( int aValue )
{
    return aValue << 1;
}

00000040 <arithmetic3>:
  40:   55                      push   %ebp
  41:   89 e5                   mov    %esp,%ebp
  43:   8b 45 08                mov    0x8(%ebp),%eax
  46:   5d                      pop    %ebp
  47:   01 c0                   add    %eax,%eax
  49:   c3                      ret    

int arithmetic4 ( int aValue )
{
    return aValue / 4;
}

00000050 <arithmetic4>:
  50:   55                      push   %ebp
  51:   89 e5                   mov    %esp,%ebp
  53:   8b 55 08                mov    0x8(%ebp),%edx
  56:   5d                      pop    %ebp
  57:   89 d0                   mov    %edx,%eax
  59:   c1 f8 1f                sar    $0x1f,%eax
  5c:   c1 e8 1e                shr    $0x1e,%eax
  5f:   01 d0                   add    %edx,%eax
  61:   c1 f8 02                sar    $0x2,%eax
  64:   c3                      ret    

int arithmetic5 ( int aValue )
{
    return aValue >> 2;
}

00000070 <arithmetic5>:
  70:   55                      push   %ebp
  71:   89 e5                   mov    %esp,%ebp
  73:   8b 45 08                mov    0x8(%ebp),%eax
  76:   5d                      pop    %ebp
  77:   c1 f8 02                sar    $0x2,%eax
  7a:   c3                      ret    

int arithmetic6 ( int aValue )
{
    return aValue * 8;
}

00000080 <arithmetic6>:
  80:   55                      push   %ebp
  81:   89 e5                   mov    %esp,%ebp
  83:   8b 45 08                mov    0x8(%ebp),%eax
  86:   5d                      pop    %ebp
  87:   c1 e0 03                shl    $0x3,%eax
  8a:   c3                      ret    

int arithmetic7 ( int aValue )
{
    return aValue << 4;
}

00000090 <arithmetic7>:
  90:   55                      push   %ebp
  91:   89 e5                   mov    %esp,%ebp
  93:   8b 45 08                mov    0x8(%ebp),%eax
  96:   5d                      pop    %ebp
  97:   c1 e0 04                shl    $0x4,%eax
  9a:   c3                      ret    

分歧是不同的 - 使用二进制补码表示,将负奇数右移一个导致不同的值将其除以2。但是编译器仍然优化了对一系列移位和加法的划分。

最明显的区别是,这对不做同样的事情 - 换四乘相当于乘以十六,而不是八!如果您让编译器为您进行小优化,您可能不会从中获得错误。

aNumber = aValue * 8;
aNumber = aValue << 4;

答案 7 :(得分:0)

如果在紧密循环的计算速度有影响的环境中进行大量计算---使用位操作。 (考虑比算术运算更快)

答案 8 :(得分:0)

当它的功率大约为2(2 ^ x)时,最好使用移位 - 它只是“推”位。 (1次装配操作,而不是2次划分)。

它的编译器是否有任何语言进行此优化?

答案 9 :(得分:0)

int i = -11;
std::cout << (i  / 2) << '\n';   // prints -5 (well defined by the standard)
std::cout << (i >> 1) << '\n';   // prints -6 (may differ on other platform)

根据所需的舍入行为,您可能更喜欢一个而不是另一个。