MENU

JavaScriptで理解するシフト演算:2進数を左右にずらす仕組み

目次

はじめに

シフト演算は、ビット演算の中でも少し難しく見えるテーマかもしれません。

<<>> のような記号を見ると、普段のWebアプリ開発ではあまり使わないため、「これは何に使うのだろう?」と感じる方も多いと思います。

しかし、シフト演算の考え方自体はそれほど複雑ではありません。
ひとことで言えば、2進数のビット列を左または右にずらす演算です。

たとえば、2進数の 00000101 は10進数で 5 を表します。
これを左に1ビットずらすと 00001010 となり、10進数では 10 になります。

console.log(5 << 1); // 10

つまり、左に1ビットシフトすると、値はおおむね2倍になります。
反対に、右に1ビットシフトすると、値はおおむね半分になります。

この「桁をずらす」という感覚は、10進数で考えるとイメージしやすくなります。
10進数では、桁を左に1つずらすと10倍になります。
同じように、2進数では、桁を左に1つずらすと2倍になります。

基本情報技術者試験でも、シフト演算は2進数やビット演算、符号付き整数、桁あふれなどと関連して出題されることがあります。
また、実務でもビットフラグ、権限管理、画像処理、バイナリデータの扱いなどを理解するうえで役立つ知識です。

この記事では、JavaScriptのサンプルコードを使いながら、シフト演算の基本を確認していきます。
<<>>>>> の違いを整理しつつ、試験対策として押さえたいポイントと、実務でどのように関係するのかを見ていきましょう。

シフト演算とは

シフト演算とは、2進数のビット列を左または右にずらす演算です。

コンピュータ内部では、数値は最終的に 01 の並びで表現されます。
たとえば、10進数の 5 は、2進数では次のように表せます。

00000101

このビット列を左に1つずらすと、次のようになります。

00001010

00001010 は、10進数では 10 です。
つまり、5 を左に1ビットシフトすると、結果は 10 になります。

JavaScriptでは、左シフトは << を使って書きます。

console.log(5 << 1); // 10

一方で、ビット列を右に1つずらすこともできます。
たとえば、10進数の 10 は2進数では次のように表せます。

00001010

これを右に1つずらすと、次のようになります。

00000101

00000101 は、10進数では 5 です。
つまり、10 を右に1ビットシフトすると、結果は 5 になります。

JavaScriptでは、右シフトは >> を使って書きます。

console.log(10 >> 1); // 5

シフト演算は、単にビット列を動かしているだけですが、数値として見ると次のような意味を持ちます。

左に1ビットシフト  → おおむね2倍
右に1ビットシフト  → おおむね半分

これは、2進数が「2を基準にした表現」だからです。
10進数で桁を左に1つずらすと10倍になるのと同じように、2進数では桁を左に1つずらすと2倍になります。

たとえば、10進数では次のように考えられます。

5  →  50

桁が左に1つ動くと、値は10倍になります。
これに対して、2進数では次のようになります。

00000101  →  00001010

桁が左に1つ動くと、値は2倍になります。

このように、シフト演算は「ビットをずらす」という操作でありながら、数値の2倍や半分と深く関係しています。

ただし、実際のコンピュータでは扱えるビット数に制限があります。
また、負の数を扱う場合は符号の考え方も関係します。

そのため、シフト演算は単純に「左なら2倍、右なら半分」と覚えるだけでなく、2進数の桁移動として理解することが大切です。

左シフト:<<

左シフトは、ビット列を左方向にずらす演算です。
JavaScriptでは、左シフト演算子として << を使います。

console.log(5 << 1); // 10

5 << 1 は、「5のビット列を左に1ビットずらす」という意味です。

10進数の 5 は、2進数では次のように表せます。

00000101

これを左に1ビットずらすと、次のようになります。

00001010

00001010 は、10進数では 10 です。
そのため、5 << 1 の結果は 10 になります。

左に2ビットずらす場合は、次のように書きます。

console.log(5 << 2); // 20

5 を左に1ビットずらすと 10、さらにもう1ビットずらすと 20 になります。

00000101  // 5
00001010  // 10
00010100  // 20

このように、正の整数では、左に1ビットシフトするたびに値はおおむね2倍になります。

console.log(3 << 1); // 6
console.log(3 << 2); // 12
console.log(3 << 3); // 24

これは、2進数が「2を基準にした表現」だからです。
10進数で桁を左に1つずらすと10倍になるように、2進数では桁を左に1つずらすと2倍になります。

JavaScriptでは、数値を2進数の文字列として確認したい場合、toString(2) を使えます。

const value = 5;

console.log(value.toString(2));      // 101
console.log((value << 1).toString(2)); // 1010
console.log((value << 2).toString(2)); // 10100

ただし、toString(2) は先頭の 0 を省略して表示します。
そのため、実際には 00000101 のようなビット列として考えていても、表示上は 101 のように見えます。

左シフトは「2倍する演算」として説明されることがありますが、正確にはビット列を左にずらす演算です。
結果として、正の整数では2倍、4倍、8倍のような値になります。

x << 1  // x を 2倍するイメージ
x << 2  // x を 4倍するイメージ
x << 3  // x を 8倍するイメージ

ただし、実際のコンピュータでは扱えるビット数に制限があります。
JavaScriptのビット演算では、数値は32ビット整数として扱われます。

そのため、非常に大きな数に対して左シフトを行うと、単純な掛け算とは違う結果になることがあります。

console.log(1 << 30); // 1073741824
console.log(1 << 31); // -2147483648

1 << 31 が負の数になるのは、JavaScriptのビット演算が32ビット符号付き整数として扱われるためです。
最上位ビットは、符号を表すために使われます。

まずは、左シフトについては次のように押さえておくとよいでしょう。

・左シフトは、ビット列を左にずらす演算
・正の整数では、1ビット左にずらすとおおむね2倍
・JavaScriptでは、32ビット整数として扱われる点に注意

右シフト:>>

右シフトは、ビット列を右方向にずらす演算です。
JavaScriptでは、右シフト演算子として >> を使います。

console.log(10 >> 1); // 5

10 >> 1 は、「10のビット列を右に1ビットずらす」という意味です。

10進数の 10 は、2進数では次のように表せます。

00001010

これを右に1ビットずらすと、次のようになります。

00000101

00000101 は、10進数では 5 です。
そのため、10 >> 1 の結果は 5 になります。

右に2ビットずらす場合は、次のように書きます。

console.log(20 >> 2); // 5

20 を2進数で表すと、次のようになります。

00010100  // 20

これを右に1ビットずらすと 10、さらにもう1ビットずらすと 5 になります。

00010100  // 20
00001010  // 10
00000101  // 5

このように、正の整数では、右に1ビットシフトするたびに値はおおむね半分になります。

console.log(24 >> 1); // 12
console.log(24 >> 2); // 6
console.log(24 >> 3); // 3

ただし、奇数を右シフトした場合は、小数部分が切り捨てられます。

console.log(5 >> 1); // 2
console.log(7 >> 1); // 3

5 / 22.5 ですが、5 >> 1 の結果は 2 です。
これは、ビットを右にずらしたとき、右端からはみ出したビットが捨てられるためです。

00000101  // 5
00000010  // 右に1ビットシフトすると 2

つまり、右シフトは「2で割る演算」と説明されることがありますが、正確にはビット列を右にずらす演算です。
その結果として、正の整数では2で割った値に近くなり、小数部分は切り捨てられます。

JavaScriptで2進数の変化を確認する場合は、左シフトと同じく toString(2) を使えます。

const value = 20;

console.log(value.toString(2));       // 10100
console.log((value >> 1).toString(2)); // 1010
console.log((value >> 2).toString(2)); // 101

toString(2) では先頭の 0 が省略されるため、表示上は短いビット列に見えます。
しかし、考え方としては、ビット列全体を右にずらしていると理解するとよいです。

x >> 1  // x を 2で割るイメージ
x >> 2  // x を 4で割るイメージ
x >> 3  // x を 8で割るイメージ

ここで注意したいのは、>>符号を考慮する右シフトだという点です。

JavaScriptのビット演算では、数値は32ビット符号付き整数として扱われます。
そのため、負の数に対して >> を使うと、符号を保つように右シフトされます。

console.log(-8 >> 1); // -4

正の数だけを見ていると、右シフトは単純に「半分にする処理」に見えます。
しかし、負の数では符号の扱いが関係するため、次の章で扱う >>> との違いが重要になります。

まずは、右シフトについては次のように押さえておきましょう。

・右シフトは、ビット列を右にずらす演算
・正の整数では、1ビット右にずらすとおおむね半分
・右端からはみ出したビットは捨てられる
・>> は符号を考慮する右シフト

論理右シフト:>>>

JavaScriptには、右シフトを行う演算子として >> のほかに、>>> も用意されています。

>>> は、論理右シフトと呼ばれる演算子です。
ビット列を右にずらす点は >> と同じですが、左側に入るビットの扱いが異なります。

まず、正の整数で確認してみましょう。

console.log(10 >> 1);  // 5
console.log(10 >>> 1); // 5

正の整数の場合、>>>>> の結果は同じになることが多いです。

10進数の 10 は、2進数では次のように表せます。

00001010

これを右に1ビットずらすと、次のようになります。

00000101

結果はどちらも 5 です。

では、>>>>> は何が違うのでしょうか。
違いがはっきり出るのは、負の数を右シフトしたときです。

console.log(-8 >> 1);  // -4
console.log(-8 >>> 1); // 2147483644

-8 >> 1-4 になります。
一方で、-8 >>> 1 は大きな正の整数になります。

これは、>> が符号を考慮する右シフトであるのに対して、>>> は符号を考慮せず、左側に 0 を入れる右シフトだからです。

JavaScriptのビット演算では、数値は32ビット整数として扱われます。
負の数は、内部的には2の補数という形式で表現されます。

たとえば、-8 を32ビットのイメージで表すと、次のようになります。

11111111111111111111111111111000

先頭のビットが 1 になっているのは、負の数を表しているためです。

>> で右シフトする場合は、符号を保つため、左側に 1 が入ります。

11111111111111111111111111111000  // -8
11111111111111111111111111111100  // -4

そのため、-8 >> 1-4 になります。

一方、>>> で右シフトする場合は、符号を保ちません。
左側には常に 0 が入ります。

11111111111111111111111111111000  // -8
01111111111111111111111111111100  // 2147483644

先頭のビットが 0 になるため、結果は正の整数として解釈されます。
その結果、-8 >>> 12147483644 という大きな値になります。

ここで、>>>>> の違いを整理しておきましょう。

>>   符号を考慮する右シフト
>>>  符号を考慮しない右シフト

もう少し具体的に言うと、次のようになります。

>>   左側に符号ビットと同じ値が入る
>>>  左側に常に 0 が入る

正の数では、符号ビットが 0 なので、>>>>> の結果は同じになりやすいです。
しかし、負の数では符号ビットが 1 なので、結果が大きく変わります。

基本情報技術者試験の文脈では、論理シフトと算術シフトの違いとして整理すると理解しやすいです。

論理シフト:空いたビットに 0 を入れる
算術シフト:符号を保つようにビットを入れる

JavaScriptの >>> は論理右シフト、>> は符号を考慮する算術右シフトに近いものとして考えるとよいでしょう。

ただし、JavaScriptの >>> は32ビット整数として処理されるため、大きな数や負の数を扱うときには直感と違う結果になることがあります。

通常のWebアプリ開発では、>>> を頻繁に使う場面は多くありません。
しかし、バイナリデータの処理やハッシュ計算、低レイヤーのコードを読むときには登場することがあります。

まずは、>>> について次のように押さえておきましょう。

・>>> は論理右シフト
・右にずらしたあと、左側には 0 が入る
・負の数に使うと、大きな正の整数になることがある
・>> との違いは、符号を保つかどうか

シフト演算と「2倍・半分」の関係

シフト演算を理解するときによく出てくるのが、次の考え方です。

左に1ビットシフトすると2倍
右に1ビットシフトすると半分

これは基本情報技術者試験でも押さえておきたい重要なポイントです。

ただし、単に「左は2倍、右は半分」と暗記するよりも、2進数の桁移動として理解しておく方が応用しやすくなります。

まず、10進数で考えてみましょう。

10進数では、桁を左に1つずらすと値は10倍になります。

5   →  50
12  →  120

これは、10進数が「10を基準にした数の表し方」だからです。

同じように、2進数では、桁を左に1つずらすと値は2倍になります。

101   →  1010

2進数の 101 は、10進数では 5 です。
これを左に1ビットシフトすると 1010 になります。

1010 は、10進数では 10 です。

console.log(5 << 1); // 10

つまり、左に1ビットシフトすると、値は2倍になります。

左に2ビットシフトすると、2倍を2回行うことになります。

console.log(5 << 1); // 10
console.log(5 << 2); // 20
console.log(5 << 3); // 40

これは、次のように考えられます。

5 << 1  → 5 × 2  = 10
5 << 2  → 5 × 4  = 20
5 << 3  → 5 × 8  = 40

一般化すると、左に n ビットシフトすることは、正の整数ではおおむね 2^n 倍することに対応します。

x << n  →  x × 2^n

一方、右シフトは逆の考え方です。

2進数の桁を右に1つずらすと、値は半分になります。

1010  →  101

2進数の 1010 は、10進数では 10 です。
これを右に1ビットシフトすると 101 になり、10進数では 5 になります。

console.log(10 >> 1); // 5

右に2ビットシフトすると、半分にする処理を2回行うイメージです。

console.log(40 >> 1); // 20
console.log(40 >> 2); // 10
console.log(40 >> 3); // 5

一般化すると、右に n ビットシフトすることは、正の整数ではおおむね 2^n で割ることに対応します。

x >> n  →  x ÷ 2^n

ただし、右シフトでは小数部分は残りません。
右端からはみ出したビットは捨てられます。

console.log(5 >> 1); // 2
console.log(7 >> 1); // 3

5 / 22.5 ですが、5 >> 12 になります。
7 / 23.5 ですが、7 >> 13 になります。

これは、右シフトが割り算そのものではなく、あくまでビット列を右にずらす演算だからです。

00000101  // 5
00000010  // 右に1ビットシフトすると 2

右端の 1 がはみ出して捨てられるため、結果は 2 になります。

ここまでを見ると、シフト演算は掛け算や割り算の代わりに使えるように見えるかもしれません。

しかし、実務では「高速化のために掛け算や割り算をシフト演算に置き換える」といった使い方は、あまり積極的にはおすすめされません。

理由は、現代のJavaScript実行環境では通常の掛け算・割り算でも十分に最適化されることが多く、シフト演算にするとかえって意図が読みにくくなる場合があるためです。

// 意図が分かりやすい
const doubled = value * 2;

// ビット操作だと分かっている場合はよいが、単なる2倍なら少し読みにくい
const shifted = value << 1;

シフト演算は、単なる計算の省略というよりも、2進数のビット列を直接操作するための演算として理解するのが大切です。

基本情報技術者試験では、次のように整理しておくとよいでしょう。

・左シフト:2進数の桁を左にずらす
・正の整数では 2^n 倍のイメージ

・右シフト:2進数の桁を右にずらす
・正の整数では 2^n で割るイメージ
・はみ出したビットは捨てられる

「2倍・半分」という結果だけでなく、なぜそうなるのかを桁移動として理解しておくと、ビット演算や補数表現の問題にもつなげやすくなります。

JavaScriptで使うときの注意点

ここまで、シフト演算を「2進数のビット列を左右にずらす演算」として見てきました。

ただし、JavaScriptでシフト演算を使うときには、いくつか注意したい点があります。
特に重要なのは、JavaScriptのビット演算では、数値が32ビット整数として扱われるという点です。

JavaScriptの数値は、基本的に Number 型で扱われます。
Number 型は整数も小数も表現できますが、ビット演算を行うときには、内部的に32ビット整数へ変換されます。

そのため、通常の数値計算と同じ感覚でシフト演算を使うと、直感と違う結果になることがあります。

たとえば、次のコードを見てみましょう。

console.log(1 << 30); // 1073741824
console.log(1 << 31); // -2147483648

1 << 30 は、1を左に30ビットシフトするので、大きな正の整数になります。

しかし、1 << 31 は負の数になります。
これは、JavaScriptのビット演算が32ビット符号付き整数として扱われるためです。

32ビット符号付き整数では、最上位ビットが符号を表します。
最上位ビットが 0 なら正の数、1 なら負の数として扱われます。

01111111111111111111111111111111  // 正の数
10000000000000000000000000000000  // 負の数

そのため、1 を左に31ビットシフトすると、最上位ビットが 1 になり、結果は負の数として解釈されます。

1 << 31  →  -2147483648

もう1つ注意したいのは、大きな数にシフト演算を使うと、情報が失われることがあるという点です。

console.log(2147483647 << 1); // -2

2147483647 は、32ビット符号付き整数で表せる最大値です。
これを左に1ビットシフトすると、32ビットの範囲を超えるため、単純に 4294967294 にはなりません。

ビット演算では、32ビットの範囲内で処理されます。
そのため、はみ出したビットは失われ、結果として -2 のような直感と違う値になります。

また、シフトするビット数にも注意が必要です。

console.log(1 << 1);  // 2
console.log(1 << 32); // 1
console.log(1 << 33); // 2

1 << 32 は、1を32ビット左にずらしているように見えます。
しかし、JavaScriptのシフト演算では、シフト数は下位5ビットだけが使われます。

つまり、32ビット単位で一周するような扱いになります。

1 << 0   // 1
1 << 1   // 2
1 << 32  // 1 と同じ
1 << 33  // 1 << 1 と同じ

そのため、シフト数が大きい場合も、見た目どおりに「32ビット分ずらす」とは考えない方がよいです。

さらに、シフト演算は小数には向いていません。

console.log(5.8 << 1); // 10

5.8 << 1 は、まず 5.8 が32ビット整数に変換されます。
このとき小数部分は切り捨てられ、5 として扱われます。

その結果、5 << 1 と同じになり、結果は 10 になります。

5.8 << 1
↓
5 << 1
↓
10

このように、JavaScriptのシフト演算では、対象の値が整数として扱われる点にも注意が必要です。

実務では、単に「2倍したい」「2で割りたい」という目的だけでシフト演算を使うのは、あまりおすすめしません。

// 意図が分かりやすい
const doubled = value * 2;
const half = Math.floor(value / 2);

// ビット操作の意図がない場合は、少し読みにくい
const doubledByShift = value << 1;
const halfByShift = value >> 1;

シフト演算は、ビットフラグやバイナリデータの処理など、ビット列を直接扱いたい場面で使うと効果的です。

一方で、通常の数値計算では、掛け算や割り算を使った方が意図が伝わりやすくなります。

JavaScriptでシフト演算を使うときは、次の点を押さえておきましょう。

・JavaScriptのビット演算は32ビット整数として扱われる
・左シフトで最上位ビットが1になると負の数になることがある
・大きな数では、はみ出したビットが失われる
・シフト数が大きい場合、見た目どおりのビット数だけずれるとは限らない
・小数は整数に変換されてから処理される

シフト演算は便利な演算ですが、JavaScriptでは通常の Number 型の計算とは異なるルールで処理されます。

そのため、使う場面では「いま扱っているのは通常の数値計算なのか、それとも32ビットのビット列なのか」を意識することが大切です。

実務での使いどころ

シフト演算は、通常のWebアプリ開発で毎日のように使う演算ではありません。

たとえば、単に数値を2倍したい場合は、次のように書いた方が分かりやすいです。

const doubled = value * 2;

あえてシフト演算を使って、次のように書く必要はあまりありません。

const doubled = value << 1;

value << 1 でも2倍に近い計算はできますが、コードを読む人にとっては「なぜビット演算を使っているのか」が分かりにくくなります。

そのため、実務では単なる掛け算や割り算の代わりにシフト演算を使うというよりも、ビット列そのものを扱う場面で使うと考えるのがよいです。

代表的な使いどころの1つが、ビットフラグです。

ビットフラグとは、複数の状態を1つの数値の中にビット単位で持たせる方法です。
たとえば、ユーザーの権限を次のように表すことができます。

const READ = 1 << 0;   // 0001
const WRITE = 1 << 1;  // 0010
const DELETE = 1 << 2; // 0100

1 << 01 << 11 << 2 のように書くことで、それぞれ異なるビット位置にフラグを立てています。

これらを組み合わせると、1つの数値で複数の権限を表せます。

const permission = READ | WRITE;

console.log(permission); // 3

READ | WRITE は、読み取り権限と書き込み権限を両方持っている状態を表します。

権限を持っているかどうかを確認する場合は、AND演算を使います。

const canRead = (permission & READ) !== 0;
const canWrite = (permission & WRITE) !== 0;
const canDelete = (permission & DELETE) !== 0;

console.log(canRead);   // true
console.log(canWrite);  // true
console.log(canDelete); // false

このように、シフト演算はフラグの位置を決めるために使い、OR演算やAND演算と組み合わせて状態を管理します。

ただし、Webアプリケーションで権限管理を実装する場合、必ずビットフラグを使うべきというわけではありません。
読みやすさや保守性を考えると、配列やオブジェクトで管理した方がよい場面も多くあります。

const permission = {
  read: true,
  write: true,
  delete: false,
};

このように書いた方が、アプリケーションのコードとしては意図が分かりやすい場合があります。

一方で、ビットフラグは、状態をコンパクトに表したい場面や、外部仕様としてビット単位の値が決まっている場面では役立ちます。

たとえば、画像処理やバイナリデータの解析では、1つの数値の中から特定のビットを取り出したり、上位ビット・下位ビットを分けたりすることがあります。

const value = 0b10101100;

const upper = value >> 4;
const lower = value & 0b00001111;

console.log(upper.toString(2)); // 1010
console.log(lower.toString(2)); // 1100

この例では、value の上位4ビットと下位4ビットを分けています。

value >> 4 によって上位4ビットを右にずらし、数値として扱いやすくしています。
また、value & 0b00001111 によって、下位4ビットだけを取り出しています。

このような処理は、ファイルフォーマット、通信データ、画像データ、圧縮形式など、低レイヤー寄りの処理で登場します。

また、色の値を扱う場面でも、シフト演算が使われることがあります。

たとえば、RGBの各成分を1つの数値にまとめる場合です。

const red = 255;
const green = 128;
const blue = 64;

const color = (red << 16) | (green << 8) | blue;

console.log(color.toString(16)); // ff8040

この例では、赤を上位8ビット、緑を中央の8ビット、青を下位8ビットに配置しています。

反対に、1つの数値からRGBの成分を取り出すこともできます。

const color = 0xff8040;

const red = (color >> 16) & 0xff;
const green = (color >> 8) & 0xff;
const blue = color & 0xff;

console.log(red);   // 255
console.log(green); // 128
console.log(blue);  // 64

このように、シフト演算は「数値を計算するため」というよりも、ビットの位置を動かして、必要な情報を取り出したり配置したりするために使われます。

普段のWebアプリ開発では、シフト演算を自分で書く機会はそれほど多くないかもしれません。

しかし、ライブラリの内部実装、ハッシュ計算、画像処理、バイナリデータ処理、権限フラグなどのコードを読むときには、シフト演算の知識が役立ちます。

実務での使いどころは、次のように整理できます。

・ビットフラグで複数の状態を1つの数値にまとめる
・バイナリデータから特定のビットを取り出す
・上位ビット・下位ビットを分ける
・RGBなどの値を1つの数値にまとめる
・低レイヤーのコードやライブラリ内部の処理を読む

シフト演算は、日常的に多用するというよりも、必要な場面で正しく読めることが大切です。

「左シフトは2倍」「右シフトは半分」という知識に加えて、ビット列のどの位置に情報を置くのか、どの位置から情報を取り出すのか、という視点で理解しておくと、実務にもつながりやすくなります。

基本情報技術者試験で押さえるポイント

シフト演算は、基本情報技術者試験でも押さえておきたいテーマです。

特に、2進数、ビット演算、符号付き整数、補数表現、桁あふれなどと関連して出題されることがあります。

まず重要なのは、シフト演算を2進数の桁移動として理解することです。

左シフト:ビット列を左にずらす
右シフト:ビット列を右にずらす

左に1ビットシフトすると、正の整数では値はおおむね2倍になります。

00000101  // 5
00001010  // 10

右に1ビットシフトすると、正の整数では値はおおむね半分になります。

00001010  // 10
00000101  // 5

そのため、試験対策としては次の関係を覚えておくとよいです。

左に n ビットシフト  →  2^n 倍のイメージ
右に n ビットシフト  →  2^n で割るイメージ

たとえば、3 を左に2ビットシフトする場合は、3 × 2^2 と考えられます。

3 << 2
= 3 × 4
= 12

JavaScriptで確認すると、次のようになります。

console.log(3 << 2); // 12

一方、20 を右に2ビットシフトする場合は、20 ÷ 2^2 と考えられます。

20 >> 2
= 20 ÷ 4
= 5

JavaScriptでは、次のように確認できます。

console.log(20 >> 2); // 5

ただし、右シフトでは、割り切れない場合に小数部分が残るわけではありません。
右端からはみ出したビットは捨てられます。

console.log(7 >> 1); // 3

7 / 23.5 ですが、7 >> 13 になります。
これは、右シフトが割り算そのものではなく、ビット列を右にずらす演算だからです。

00000111  // 7
00000011  // 3

次に注意したいのが、符号付き整数です。

右シフトには、符号を保つものと、符号を考慮しないものがあります。

算術右シフト:符号を保つ
論理右シフト:空いたビットに 0 を入れる

JavaScriptでは、>> が符号を考慮する右シフト、>>> が論理右シフトです。

console.log(-8 >> 1);  // -4
console.log(-8 >>> 1); // 2147483644

>> は符号を保つように右シフトします。
一方、>>> は左側に 0 を入れるため、負の数に使うと大きな正の整数になることがあります。

基本情報技術者試験では、JavaScriptの演算子そのものが問われるというよりも、次のような考え方が重要になります。

・論理シフトでは、空いたビットに 0 を入れる
・算術シフトでは、符号を保つように空いたビットを埋める

また、桁あふれにも注意が必要です。

コンピュータでは、扱えるビット数が決まっています。
そのため、左シフトによって上位のビットが範囲外にはみ出すと、値が直感と違うものになることがあります。

JavaScriptでは、ビット演算が32ビット整数として扱われるため、次のような結果になります。

console.log(1 << 31); // -2147483648

これは、最上位ビットが符号ビットとして扱われるためです。

試験では、特定の言語仕様よりも、固定長のビット列で何が起きるかを意識することが大切です。

・ビット数には上限がある
・左シフトでは上位ビットがはみ出すことがある
・符号付き整数では最上位ビットが符号に関係する

シフト演算の問題では、計算だけでなく、ビット列を書いて確認すると理解しやすくなります。

たとえば、次のように考えるとミスを減らせます。

1. 10進数を2進数にする
2. 指定された方向にビットをずらす
3. はみ出したビットを捨てる
4. 空いたビットを 0 または符号ビットで埋める
5. 必要に応じて10進数に戻す

最後に、試験対策として押さえるポイントをまとめます。

・左シフトは、正の整数では 2^n 倍のイメージ
・右シフトは、正の整数では 2^n で割るイメージ
・右シフトでは、はみ出したビットが捨てられる
・論理シフトは、空いたビットに 0 を入れる
・算術シフトは、符号を保つように空いたビットを埋める
・固定長のビット列では、桁あふれに注意する

シフト演算は、記号だけを見ると難しく感じます。
しかし、2進数の桁を左右に動かしているだけだと考えると、問題の見通しがよくなります。

「何倍になるか」だけでなく、「ビット列がどう動くか」を意識して理解しておきましょう。

まとめ

この記事では、JavaScriptのサンプルコードを使いながら、シフト演算の基本を見てきました。

シフト演算とは、2進数のビット列を左または右にずらす演算です。

JavaScriptでは、主に次の3つの演算子を使います。

<<   左シフト
>>   符号を考慮する右シフト
>>>  論理右シフト

左シフト << は、ビット列を左にずらす演算です。
正の整数では、1ビット左にずらすと、おおむね2倍になります。

console.log(5 << 1); // 10
console.log(5 << 2); // 20

右シフト >> は、ビット列を右にずらす演算です。
正の整数では、1ビット右にずらすと、おおむね半分になります。

console.log(10 >> 1); // 5
console.log(20 >> 2); // 5

ただし、右端からはみ出したビットは捨てられるため、割り算と完全に同じではありません。

console.log(7 >> 1); // 3

7 / 23.5 ですが、7 >> 13 になります。

また、JavaScriptには論理右シフト >>> もあります。
>> は符号を考慮しますが、>>> は左側に常に 0 を入れます。

console.log(-8 >> 1);  // -4
console.log(-8 >>> 1); // 2147483644

正の数では >>>>> の結果が同じになりやすいですが、負の数では大きく変わります。

JavaScriptでシフト演算を使うときは、ビット演算が32ビット整数として扱われる点にも注意が必要です。

console.log(1 << 31); // -2147483648

通常の Number 型の計算とは違い、ビット演算では32ビットの範囲で処理されます。
そのため、大きな数や負の数、小数を扱うときには、直感と違う結果になることがあります。

実務では、単に2倍したい、半分にしたいという目的だけでシフト演算を使う場面は多くありません。
そのような場合は、掛け算や割り算を使った方が意図が伝わりやすいです。

const doubled = value * 2;
const half = Math.floor(value / 2);

一方で、ビットフラグ、バイナリデータ処理、画像処理、RGB値の操作、ライブラリ内部の実装などでは、シフト演算の知識が役立ちます。

基本情報技術者試験の対策としては、次のポイントを押さえておきましょう。

・左シフトは、正の整数では 2^n 倍のイメージ
・右シフトは、正の整数では 2^n で割るイメージ
・右シフトでは、はみ出したビットが捨てられる
・論理シフトでは、空いたビットに 0 を入れる
・算術シフトでは、符号を保つように空いたビットを埋める
・固定長のビット列では、桁あふれに注意する

シフト演算は、記号だけを見ると難しく感じるかもしれません。
しかし、やっていることは「2進数の桁を左右に動かす」ことです。

2倍半分 という結果だけを覚えるのではなく、ビット列がどのように動いているのかを意識すると、シフト演算はぐっと理解しやすくなります。

JavaScriptのコードで実際に動かしながら、2進数、ビット演算、符号付き整数の理解につなげていきましょう。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

20代前半に、ゲームボーイアドバンス、ニンテンドーDS、Wii向けソフトの開発に携わりました。

その後、20代後半にかけては組み込み系エンジニアとして、主にサーバーソフトウェアの開発を経験。

30代からはWebエンジニアとして、さまざまなWebサービスの開発に携わってきました。

現在は40代となり、ゲーム開発、組み込み開発、Web開発で培った経験を活かしながら、技術をわかりやすく伝える活動にも取り組んでいます。

コメント

コメントする

CAPTCHA


目次