C++ のビットシフト演算子

Anam Javed 2023年10月12日
  1. C++ の左ビットシフト演算子
  2. C++ の右ビットシフト演算子
  3. C++ での Float データ型のビットシフト
  4. C++ での配列のビットシフト
  5. C++ でのビットシフトとマスク
  6. C++ での負の数のビットシフト
  7. C++ で Long を使用したビットシフト
  8. まとめ
C++ のビットシフト演算子

C++ では、ビットシフト演算子はその名前が示すとおりにビットをシフトします。プログラムの要件に従って、ビット単位のシフト演算子はバイナリビットを左または右にシフトします。

これらの演算子には整数値が適用されます(int、long、場合によっては short、byte または char)。一部の言語では、int より小さいデータ型でシフト演算子を使用すると、オペランドのサイズが自動的に int に変更されます。

この記事では、C++ での左シフト演算子と右シフト演算子、およびそれらの実装について、関連する例とともに詳しく説明します。

C++ の左ビットシフト演算子

左シフト演算子は、シフト式のビットを加法式の桁数だけ左にシフトします。シフト演算によって空いたビット位置はゼロで埋められ、符号ビットを含め、最後からシフトされたビットは破棄されます。

左シフト演算子は 2つの数値を取ります。これにより、第 1 オペランドのビットがシフトされ、第 2 オペランドがシフトする場所の数を決定します。

(a<<b) で表される整数 a を整数 b で左シフトすることは、a2^b(2 を b の累乗)で乗算することと同等であると言えます。左シフト演算子は << で示されます。

たとえば、M<<k。ここで、M は第 1 オペランド、k は第 2 オペランドです。

M=33; を取りましょうこれはバイナリで 100001 であり、k = 2 です。M を 2 だけ左シフトすると、M=M<<2 で表され、M=M(2^2) になります。

したがって、M=33(2^2)=132 は 10000100 と書くことができます。

例:

#include <iostream>
using namespace std;

int main() {
  unsigned char x = 6, y = 7;
  cout << "x<<1 = " << (x << 1) << endl;
  cout << "y<<1 = " << (y << 1) << endl;
  return 0;
}

出力:

x<<1 = 12
y<<1 = 14

上記のコードで、符号なしの char x および char y 変数は、変数がメモリの 8 ビットすべてを使用し、符号ビット(signed char にある)がない文字データ型を示します。

ここで、char x は 6、つまりバイナリで 00000110 に等しく、char y は 7、つまりバイナリで 00000111 に等しくなります。

最初の print ステートメントは、x の値を 1 ビット左シフトすることを示しています。結果は 00001100 です。2 番目の print ステートメントは、y の値を 1 ビット左シフトするように指示しています。結果は 00001110 です。

C++ の右ビットシフト演算子

右シフト演算子は、シフト式のビットパターンを、加法式が右に提供する桁数だけシフトします。シフト操作によって空けられたビット位置は、符号なしの値に対してゼロで埋められます。

符号ビットは、符号付き数値の空のビット位置を置き換えます。数値が正の場合、値 0 が使用されます。数値が負の場合、値 1 が使用されます。

右シフト演算子は 2つの数値を取ります。これにより、第 1 オペランドのビットがシフトされ、第 2 オペランドがシフトする場所の数を決定します。

(a>>b) で表される整数 a を整数 b で右シフトすることは、a2^b(2 の累乗 b)で除算することと同等であると言えます。右シフト演算子は次のように表されます:>>

たとえば、M>>k 。ここで、M は最初のオペランドであり、k は 2 番目のオペランドです。

M=32; を取りましょうこれはバイナリで 100000 で、k = 2 です。M が 2 だけ右シフトされ、M=M>>2 で示される場合、MM=M/(2^2) になります。したがって、M=32/(2^2)=8 は 1000 と書くことができます。

プログラム例:

#include <iostream>

int main() {
  unsigned char x = 6, y = 9;
  cout << "a>>1 = " << (a >> 1) << endl;
  cout << "b>>1 = " << (b >> 1) << endl;
  return 0;
}

出力:

x>>1 = 3
y>>1 = 4

上記のコードで、符号なしの char x および char y 変数は、メモリの 8 ビットすべてを使用する変数の文字データ型を示し、符号ビットはありません(符号付き char にあります)。

ここで、char x は 6、つまりバイナリでは 00000110 に等しく、char y は 9、つまりバイナリでは 00001001 に等しくなります。

最初の print ステートメントは、x の値を 1 ビット右シフトすることを示しています。結果は 00000011 です。2 番目の print ステートメントは、y の値を 1 ビット右シフトすることを示しています。結果は 00000100 です。

C++ での Float データ型のビットシフト

エラーが表示されるため、C++ で float をビットシフトすることはできませんが、それはなぜですか?フロートは特殊な形式で保存されているためです。

フロートの 32 ビットは、仮数と指数の 2つのカテゴリに分類されます。シフトは、ビットを指数カテゴリから仮数カテゴリに、またはその逆にシフトする可能性があります。

例:

#include <stdio.h>

int main(int ar, char *arg[]) {
  float testFl = 2.5;

  printf("testFloat (before): %f\n", testFl);
  testFl = testFl << 1;
  printf("testFloat (after): %f\n", testFl);
  return 0;
}

出力:

error: invalid operands to binary << (have 'float' and 'int')

右シフトまたは左シフトでは、すべてのビットが折りたたまれます。

C++ での配列のビットシフト

サイズ n の配列 ar[] と整数 m があります。

目標は、存在するすべての配列要素に対して右シフト操作を実行することにより、すべての配列要素を > m にすることです。それができない場合は、-1 を出力します。

例:

Input: ar[] = { 21, 22, 23, 19 }, m = 34
Output: { 26, 26, 27, 25 }

Explanation:
ar[0] = 10101
After 1 right shift, 11010 → 26
ar[1] = 10110
After 3 right shift, 11010 → 26
ar[2] = 10111
After  1 right shift, 11011 → 27
ar[3] = 10011
After 2 right shift, 11001 → 25

コード:

#include <bits/stdc++.h>
using namespace std;

int setBitNumber(int n) {
  int m = log2(n);
  return m;
}

bool check(int ar[], int m, int n) {
  for (int i = 0; i < n; i++) {
    if (ar[i] <= m) return false;
  }
  return true;
}

void modifyArray(int ar[], int m, int n) {
  for (int i = 0; i < n; i++) {
    if (ar[i] > m)
      continue;
    else {
      int bits = setBitNumber(ar[i]);
      int el = ar[i];
      for (int j = 0; j < bits; j++) {
        if (el & 1) {
          el >>= 1;
          el |= (1 << bits);
        } else {
          el >>= 1;
        }
        if (el > m) {
          arr[i] = el;
          break;
        }
      }
    }
  }

  if (check(ar, m, n)) {
    for (int i = 0; i < n; i++) cout << ar[i] << " ";
  } else
    cout << -1;
}

int main() {
  int ar[] = {21, 22, 23, 19};
  int n = sizeof(ar) / sizeof(ar[0]);
  int m = 24;
  modifyArray(ar, m, n);
  return 0;
}

出力:

[26, 26, 27, 25]

プログラムで実行される主な操作は、配列のトラバースです。配列 ar[i] の各要素に対して右シフト操作を実行します。

ar[i] > m の場合に条件がチェックされます。true の場合は、配列 ar[i] を更新し、そうでない場合は続行します。

配列 ar[i] ≤ m のいずれかの要素の場合は-1 を出力し、そうでない場合は配列 ar[i] を出力します。

C++ でのビットシフトとマスク

マスクは、保持するビットとクリアするビットを指定します。

例:

Mask:   00001111b
Value:  01010101b

値にマスクを適用するとき、最後の(下の)4 ビットを維持しながら、最初の(上の)4 ビットをクリアしたいと思います。その結果、下位 4 ビットを取得しました。

出力:

Mask:   00001111b
Value:  01010101b
Result: 00000101b

ビットシフト演算子は、ビットを 1つずつ剥がすために、マスキング操作で頻繁に使用されます。次の例では、unsignedchar を個別のビットの配列に分割する方法を説明します。

unsigned char y = 0xD5;
unsigned char bit[8];
unsigned char mask = 1;
for (int x = 7; x >= 0; x--) {
  bits[x] = y & mask;
  y = y >> 1;
}

C++ での負の数のビットシフト

左シフト演算子と右シフト演算子を使用して負の数を入力しないでください。いずれかのオペランドが負の整数の場合、結果は未定義の動作になります。

たとえば、1 >> -11 << -1 の両方の結果は定義されていません。

#include <iostream>

int main() {
  unsigned char x = -6, cout << "a>>1 = " << (a >> 1) << endl;
  return 0;
}

出力:

error: undefined behavior in C

C++ で Long を使用したビットシフト

データ型 long は、32 ビットまたは 64 ビットのビットシフトに使用されます。

例:

32 ビットの場合、

unsigned long A = (1L << 37)

64 ビットの場合、

unsigned long long A = (1ULL << 37);

プログラムを使用して以下を実装するには:

#include <stdio.h>

int main(void) {
  long long y = 1ULL;

  // Left shift 40 times
  y <<= 20;
  y <<= 20;

  printf("y is %lld\n", y);
  return 0;
}

出力:

y is 1099511627776

ここでは、64 ビット変数 long long y が使用され、1ULL は符号なし long long int 定数(64 ビット)です。変数 y は 40 回シフトされ、出力されます。

まとめ

この記事では、C++ のビットシフト演算子について説明しました。C++ の左右のシフト演算子について詳しく学びました。

関連記事 - C++ Operator