
C言語のきほん|論理シフトと算術シフトの基礎
同じ右シフトでも意味が変わる。論理シフトと算術シフトの違いを知ると、ビット操作がぐっとクリアになる。
ビット演算やシフト演算を学び始めると、まず 左シフト と 右シフト という言葉が出てきます。
ここまでは比較的イメージしやすいのですが、少し学習を進めると、今度は 論理シフト と 算術シフト という似た名前が登場します。
この2つは、どちらもビットを左右にずらす操作ですが、考え方と目的が少し違います。
論理シフトは、ビット列をそのまま機械的にずらすイメージです。
一方、算術シフトは、符号付き整数の意味をできるだけ保ちながらずらすイメージです。
この違いは、特に右シフトで重要になります。
正の数ではあまり気にならなくても、負の数を右にずらしたときに、左側を 0 で埋めるのか、それとも符号ビットで埋めるのかによって、結果の意味が変わってきます。
C言語では、シフト演算子として << と >> が用意されていますが、負の数のシフトについては処理系依存になる部分があります。
そのため、ただ「右にずらすだけ」と考えてしまうと、思わぬ動作の違いに出会うことがあります。
ここでは、論理シフトと算術シフトの違いを、まず図と表でやさしく整理しながら、C言語での扱い方まで丁寧に見ていきましょう。
ビットをただ動かすのではなく、「どのように埋めるのか」「数値としてどういう意味になるのか」を意識できるようになると、シフト演算の理解がぐっと深まります。
シフト演算とは何か
シフト演算とは、データのビット列を左または右にずらす演算です。
ビット列を動かすことで、特定のビット位置を調整したり、値を 2倍、4倍、2分の1、4分の1 のように変化させたりできます。
シフト演算には、大きく分けて次の2種類があります。
| 種類 | 特徴 |
|---|---|
| 論理シフト | 符号を意識せず、ビットを単純にずらす |
| 算術シフト | 符号ビットを意識して、数値の意味を保ちながらずらす |
さらに、それぞれに左シフトと右シフトがあります。
シフト演算の種類を整理する
まずは全体像をつかみやすいように、種類を整理して見てみましょう。
| シフト演算 | 内容 |
|---|---|
| 論理左シフト | 左へずらし、右側を 0 で埋める |
| 論理右シフト | 右へずらし、左側を 0 で埋める |
| 算術左シフト | 左へずらし、右側を 0 で埋める |
| 算術右シフト | 右へずらし、左側を符号ビットで埋める |
ここで見ると、左シフトは論理でも算術でも見た目がかなり似ています。
大きく違いが出るのは、右シフトのときに左側を何で埋めるか です。
論理シフトとは何か
論理シフトは、ビット列を単純に左右へずらす操作です。
数値の符号は考えず、ビットの位置だけを機械的に移動します。
論理シフトでは、空いたビットは常に 0 で埋められます。
そのため、ビット列そのものをデータとして扱いたいときに向いています。
論理左シフト
論理左シフトでは、ビットを左へずらし、右側の空いたビットには 0 が入ります。
左端からあふれたビットは捨てられます。
たとえば、8ビットの値が次のようだったとします。
1 0 0 1 0 0 0 1これを2ビット論理左シフトすると、次のようになります。
0 1 0 0 0 1 0 0右側には 0 が入り、左からあふれたビットは捨てられています。
論理左シフトの特徴
| 項目 | 内容 |
|---|---|
| ビットの移動方向 | 左 |
| 右側の空き | 0で埋める |
| 左端のあふれ | 捨てる |
論理右シフト
論理右シフトでは、ビットを右へずらし、左側の空いたビットには 0 が入ります。
右端からあふれたビットは捨てられます。
同じく、次のビット列を考えてみます。
1 0 0 1 0 0 0 1これを2ビット論理右シフトすると、次のようになります。
0 0 1 0 0 1 0 0左側には 0 が入り、右からはみ出したビットは捨てられています。
論理右シフトの特徴
| 項目 | 内容 |
|---|---|
| ビットの移動方向 | 右 |
| 左側の空き | 0で埋める |
| 右端のあふれ | 捨てる |
論理シフトのポイント
論理シフトは、あくまでビット列の位置を変える操作です。
そのため、整数としての符号を保つことは考えていません。
この性質は、符号なし整数やビット列データを扱うときにとても扱いやすいです。
特に、マスク操作や特定ビットの抽出では、論理シフトの考え方が基本になります。
算術シフトとは何か
算術シフトは、符号付き整数の意味をできるだけ保ちながらシフトする操作です。
特に負の数を扱うときに重要になります。
算術シフトでは、右シフトのときに左側を 符号ビット で埋めるのが大きな特徴です。
これによって、負の数を右にずらしても、負の数らしい形を保ちやすくなります。
算術左シフト
算術左シフトでは、ビットを左へずらし、右側の空いたビットには 0 が入ります。
左端のあふれたビットは捨てられます。
ここは見た目としては論理左シフトとほぼ同じです。
たとえば、8ビットで 4 を表すビット列を考えてみます。
0 0 0 0 0 1 0 0 (4)これを2ビット左へずらすと、
0 0 1 0 0 0 0 0 (16)になります。
4 が 16 になっているので、これは 2² = 4倍 になったと考えられます。
算術左シフトの特徴
| 項目 | 内容 |
|---|---|
| ビットの移動方向 | 左 |
| 右側の空き | 0で埋める |
| 左端のあふれ | 捨てる |
| 数値の見方 | あふれがなければ 2ⁿ倍に相当 |
算術右シフト
算術右シフトでは、ビットを右へずらし、左側の空いたビットには 符号ビット が入ります。
右端からあふれたビットは捨てられます。
ここが論理右シフトとの最大の違いです。
たとえば、8ビットで -4 を表すビット列を考えます。
1 1 1 1 1 1 0 0 (-4)これを2ビット右へずらすと、算術シフトでは左側に 1 が入ります。
1 1 1 1 1 1 1 1 (-1)このように、負の数の符号を維持したまま右へずらそうとするのが算術右シフトです。
ただし、ここで少し注意が必要です。
算術右シフトは「だいたい 2ⁿ で割るような動き」をしますが、常に数学的にきれいな除算と完全一致するとは限りません。
ビット列の扱いであることを忘れずに見ることが大切です。
論理シフトと算術シフトの違いを整理する
ここまでの内容を表でまとめると、違いが見やすくなります。
| 種類 | 左シフト時の空き | 右シフト時の空き | 符号を意識するか |
|---|---|---|---|
| 論理シフト | 0で埋める | 0で埋める | 意識しない |
| 算術シフト | 0で埋める | 符号ビットで埋める | 意識する |
この表を見ると、特に重要なのは右シフトだとわかります。
左シフトは見た目が似ていますが、右シフトでは埋め方の違いがはっきり表れます。
C言語でのシフト演算の考え方
C言語では、シフト演算子として << と >> が使えます。
ただし、論理シフトか算術シフトかを明示的に書き分ける演算子があるわけではありません。
そのため、実際の動作は、値の型や処理系によって決まる という理解が大切です。
C言語で意識したい基本
| 場面 | 考え方 |
|---|---|
| 符号なし整数の右シフト | 通常は論理シフトとして考えやすい |
| 符号付き整数の右シフト | 処理系依存なので注意が必要 |
| 正の数のシフト | 比較的直感通りに動きやすい |
| 負の数のシフト | 結果に注意が必要 |
特に、負の数の右シフトは処理系依存です。
環境によっては算術シフトになることが多いですが、常にそうだと決めつけるのは危険です。
正の数では比較的わかりやすい
正の数に対するシフトは、比較的イメージしやすいです。
- 左シフトは、あふれがなければ 2ⁿ倍
- 右シフトは、あふれがなければ 2ⁿで割る
という関係で見られることが多いです。
たとえば、8 を2ビット左シフトすると 32 になります。
また、8 を2ビット右シフトすると 2 になります。
このように、正の数ではシフトの意味がかなりつかみやすいです。
負の数では慎重に考える
負の数になると、単純に「右にずらしたら割り算」とは言い切れなくなります。
特に符号付き整数では、左側をどう埋めるかが重要になるからです。
また、C言語では負の数の右シフトが処理系依存なので、コードの移植性を考えるなら、負の数に対するシフトは慎重に扱う必要があります。
そのため、ビット操作を安全に行いたいときは、まず 符号なし整数を使う という考え方がとても大切です。
サンプルプログラムで確認する
符号なし整数と符号付き整数の右シフトの違いを確認しやすいシンプルなプログラム例です。
ファイル名:16_8_1.c
#include <stdio.h>
#include <stdint.h>
int main(void)
{
/* 論理シフトと算術シフトの違いを確認する */
uint8_t a = 0x91;
int8_t b = -4;
printf("シフトの違いを確認してみましょう。\n\n");
printf("符号なし整数 a = 0x%02X を2ビット右シフトすると 0x%02X です。\n",
a, (uint8_t)(a >> 2));
printf("符号付き整数 b = %d を2ビット右シフトすると %d です。\n",
b, (int8_t)(b >> 2));
return 0;
}このサンプルプログラムでわかること
このプログラムでは、符号なし整数と符号付き整数をそれぞれ右シフトしています。
まず、uint8_t 型の a は符号なし整数なので、右シフトでは左側が 0 で埋まりやすく、論理右シフトのイメージで理解しやすいです。
一方、int8_t 型の b は符号付き整数です。
そのため、右シフトの結果は環境に依存する可能性があります。
多くの環境では算術右シフトとなり、符号ビットが保たれますが、それを前提にしすぎないことが大切です。
この例を見ると、同じ >> という演算子でも、型によって見方が変わることがよくわかります。
シフトの学習で特に意識したいこと
論理シフトと算術シフトを学ぶときは、次の点を意識すると整理しやすいです。
| 意識したいこと | 内容 |
|---|---|
| 論理シフト | 符号を考えず、空きは 0 で埋める |
| 算術シフト | 符号ビットを意識して埋める |
| 左シフト | 論理でも算術でも見た目は似やすい |
| 右シフト | 論理と算術の違いがはっきり出る |
| C言語での安全な考え方 | 符号なし整数で扱うと理解しやすい |
シフト演算は、単にビットを左右に動かすだけの操作ではありません。
「空いた場所を何で埋めるか」によって、数値としての意味が変わることが大きなポイントです。
この視点を持てるようになると、シフト演算子を使ったコードを読んだときにも、「これは単なるビット移動なのか」「符号を保とうとしているのか」が見えてきます。
そこまでわかるようになると、ビット操作の理解はかなり深まってきています。
