开发者

What is the difference between bit shifting and arithmetical operations?

开发者 https://www.devze.com 2023-01-02 07:26 出处:网络
int aNumber; aNu开发者_如何转开发mber = aValue / 2; aNumber = aValue >> 1; aNumber = aValue * 2;
int aNumber;

aNu开发者_如何转开发mber = aValue / 2;
aNumber = aValue >> 1;

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

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

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

// etc.

Whats is the "best" way to do operations? When is better to use bit shifting?


The two are functionally equivalent in the examples you gave (except for the final one, which ought to read aValue * 8 == aValue << 3), if you are using positive integers. This is only the case when multiplying or dividing by powers of 2.

Bit shifting is never slower than arithmetic. Depending on your compiler, the arithmetic version may be compiled down to the bit-shifting version, in which case they both be as efficient. Otherwise, bit-shifting should be significantly faster than arithmetic.

The arithmetic version is often more readable, however. Consequently, I use the arithmetic version in almost all cases, and only use bit shifting if profiling reveals that the statement is in a bottleneck:

Programs should be written for people to read, and only incidentally for machines to execute.


The difference is that arithmetic operations have clearly defined results (unless they run into signed overflow that is). Shift operations don't have defined results in many cases. They are clearly defined for unsigned types in both C and C++, but with signed types things quickly get tricky.

In C++ language the arithmetical meaning of left-shift << for signed types is not defined. It just shifts bits, filling with zeros on the right. What it means in arithmetical sense depends on the signed representation used by the platform. Virtually the same is true for right-shift >> operator. Right-shifting negative values leads to implementation-defined results.

In C language things are defined slightly differently. Left-shifting negative values is impossible: it leads to undefined behavior. Right-shifting negative values leads to implementation-defined results.

On most practical implementations each single right-shift performs division by 2 with rounding towards negative infinity. This, BTW, is notably different from the arithmetic division / by 2, since typically (and always in C99) of the time it will round towards 0.

As for when you should use bit-shifting... Bit-shifting is for operations that work on bits. Bit-shifting operators are very rarely used as a replacement for arithmetic operators (for example, you should never use shifts to perform multiplication/division by constant).


Bit shifting is a 'close to the metal' operation that most of the time doesn't contain any information on what you really want to achieve.

If you want to divide a number by two, by all means, write x/2. It happens to be achieved by x >> 1, but the latter conceals the intent.

When that turns out to become a bottleneck, revise the code.


Whats is the "best" way to do operations?

Use arithmetic operations when dealing with numbers. Use bit operations when dealing with bits. Period. This is common sense. I doubt anyone would ever think using bit shift operations for ints or doubles as a regular day-to-day thing is a good idea.

When is better to use bit shifting?

When dealing with bits?

Additional question: do they behave the same in case of arithmetic overflow?

Yes. Appropriate arithmetic operations are (often, but not always) simplified to their bit shift counterparts by most modern compilers.

Edit: Answer was accepted, but I just want to add that there's a ton of bad advice in this question. You should never (read: almost never) use bit shift operations when dealing with ints. It's horrible practice.


When your goal is to multiply some numbers, using arithmetic operators makes sense.

When your goals is to actually logically shift the bits, then use the shift operators.

For instance, say you are splitting the RGB components from an RGB word, this code makes sense:

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

on the other hand when you want to multiply some value with 4 you should really code your intent, and not do shifts.


As long as you are multiplying or dividing within the 2er powers it is faster to operate with a shift because it is a single operation (needs only one process cycle).
One gets used to reading << 1 as *2 and >>2 as /4 quite quickly so I do not agree with readability going away when using shifting but this is up to each person.

If you want to know more details about how and why, maybe wikipedia can help or if you want to go through the pain learn assembly ;-)


As an example of the differences, this is x86 assembly created using gcc 4.4 with -O3

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    

The divisions are different - with a two's complement representation, shifting a negative odd number right one results in a different value to dividing it by two. But the compiler still optimises the division to a sequence of shifts and additions.

The most obvious difference though is that this pair don't do the same thing - shifting by four is equivalent to multiplying by sixteen, not eight! You probably would not get a bug from this if you let the compiler sweat the small optimisations for you.

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


If you have big calculations in a tight loop kind of environment where calculation speed has an impact --- use bit operations. ( considered faster than arithmetic operations)


When its about power 2 numbers (2^x), its better to use shifts - it's just to 'push' the bits. (1 assembly operation instead of 2 in dividing).

Is there any language which its compiler does this optimization?


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)

Depending on the desired rounding behavior, you may prefer one over the other.

0

精彩评论

暂无评论...
验证码 换一张
取 消