PHPのビット演算子について

採用はこちら

PHPのビット演算子とは、整数を2進数のビット列として扱い、各ビット単位で計算するための演算子です。

通常の加算や減算が「数値そのもの」を計算するのに対して、ビット演算子は「数値を構成している0と1の並び」を直接操作します。

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

5 // 2進数では 101

8ビットで表すと、次のようになります。

00000101

また、10進数の 3 は2進数では次のようになります。

00000011

ビット演算では、このような0と1の並びを、桁ごとに比較したり、反転させたり、左右にずらしたりします。

PHPでビット演算子を使う主な場面は、権限管理、機能フラグ、エラー設定、外部APIのオプション指定などです。

特に、複数のON/OFF状態を1つの整数にまとめたい場合に便利です。

目次

PHPで使えるビット演算子一覧

PHPで使える主なビット演算子は、次の6種類です。

演算子名前意味
&ビットAND両方のビットが 1 の場合だけ 1
``ビットOR
^ビットXOR片方だけが 1 の場合に 1
~ビットNOTビットを反転する
<<左シフトビットを左にずらす
>>右シフトビットを右にずらす

それぞれの演算子について、具体例を使いながら詳しく見ていきます。

&:ビットAND

& は、左右の値をビット単位で比較し、両方のビットが 1 の場合だけ 1 にする演算子です。

& の基本的な使い方

$a = 5; // 0101
$b = 3; // 0011

echo $a & $b; // 1

2進数で見ると、次のようになります。

  0101  // 5
& 0011  // 3
------
  0001  // 1

各桁の計算は、次のようになります。

0 & 0 = 0
1 & 0 = 0
0 & 1 = 0
1 & 1 = 1

そのため、結果は 0001、つまり10進数で 1 になります。

& の主な使い道

& は、特定のフラグが立っているかを確認するときによく使います。

const READ  = 1; // 0001
const WRITE = 2; // 0010
const EXEC  = 4; // 0100

$permission = READ | WRITE;

if (($permission & READ) !== 0) {
    echo '読み取り権限があります';
}

この例では、$permissionREAD のビットが含まれているかを & で確認しています。

より厳密に書く場合は、次のようにします。

if (($permission & READ) === READ) {
    echo '読み取り権限があります';
}

単一フラグの確認であれば !== 0 でもよく使われますが、複数フラグをまとめて確認する場合は === で比較する書き方が安全です。

|:ビットOR

| は、左右の値をビット単位で比較し、どちらか一方でも 1 なら 1 にする演算子です。

| の基本的な使い方

$a = 5; // 0101
$b = 3; // 0011

echo $a | $b; // 7

2進数で見ると、次のようになります。

  0101  // 5
| 0011  // 3
------
  0111  // 7

各桁の計算は、次のようになります。

0 | 0 = 0
1 | 0 = 1
0 | 1 = 1
1 | 1 = 1

そのため、結果は 0111、つまり10進数で 7 になります。

| の主な使い道

| は、複数のフラグをまとめるときによく使います。

const READ  = 1; // 0001
const WRITE = 2; // 0010
const EXEC  = 4; // 0100

$permission = READ | WRITE;

echo $permission; // 3

ビットで見ると、次のようになります。

  0001  // READ
| 0010  // WRITE
------
  0011  // READ + WRITE

このように、READWRITE を1つの整数にまとめて管理できます。

^:ビットXOR

^ は、左右のビットを比較し、片方だけが 1 の場合に 1 にする演算子です。

^ の基本的な使い方

$a = 5; // 0101
$b = 3; // 0011

echo $a ^ $b; // 6

2進数で見ると、次のようになります。

  0101  // 5
^ 0011  // 3
------
  0110  // 6

各桁の計算は、次のようになります。

0 ^ 0 = 0
1 ^ 0 = 1
0 ^ 1 = 1
1 ^ 1 = 0

^ は「左右のビットが違っていれば 1、同じなら 0」と考えるとわかりやすいです。

^ の主な使い道

^ は、特定のフラグを切り替えるときに使えます。

const ADMIN = 1; // 0001

$status = 0;

// ADMINをONにする
$status = $status ^ ADMIN;

echo $status; // 1

// もう一度実行するとADMINがOFFになる
$status = $status ^ ADMIN;

echo $status; // 0

XORには、同じビットを2回当てると元に戻る性質があります。

そのため、特定の状態をON/OFFで切り替えたい場合に便利です。

~:ビットNOT

~ は、ビットを反転する演算子です。

01 に、10 になります。

~ の基本的な使い方

$a = 5;

echo ~$a; // -6

ここで注意したいのは、~5 の結果が 10 ではなく -6 になることです。

「5は2進数で 0101 だから、反転すると 1010、つまり10ではないか」と思うかもしれません。

しかしPHPの整数は、実行環境に応じた固定幅の符号付き整数として扱われます。

多くの64ビット環境では、5 は内部的に次のようなイメージで扱われます。

00000000 ... 00000101

これを反転すると、次のようになります。

11111111 ... 11111010

符号付き整数では、上位ビットが 1 になると負の数として解釈されるため、結果は -6 になります。

一般的には、整数 n に対して次の関係になります。

~n = -(n + 1)

そのため、次の結果になります。

echo ~5; // -6

~ の主な使い道

~ は、特定のフラグを外すときによく使います。

const READ  = 1; // 0001
const WRITE = 2; // 0010
const EXEC  = 4; // 0100

$permission = READ | WRITE | EXEC; // 0111

$permission = $permission & ~WRITE;

echo $permission; // 5

ビットで見ると、次のような考え方です。

$permission = 0111
WRITE       = 0010
~WRITE      = 1101

  0111
& 1101
------
  0101

結果として、WRITE のビットだけが外れます。

実務では、次のように省略して書くことが多いです。

$permission &= ~WRITE;

<<:左シフト

<< は、ビットを左にずらす演算子です。

<< の基本的な使い方

$a = 3; // 0011

echo $a << 1; // 6

2進数で見ると、次のようになります。

0011  // 3
↓ 左に1つシフト
0110  // 6

左に1つシフトすると、基本的には2倍になります。

echo 3 << 1; // 6
echo 3 << 2; // 12
echo 3 << 3; // 24

これは、次のような意味です。

3 << 1 = 3 * 2
3 << 2 = 3 * 4
3 << 3 = 3 * 8

<< の主な使い道

左シフトは、ビットフラグの値を定義するときによく使います。

const READ  = 1 << 0; // 1
const WRITE = 1 << 1; // 2
const EXEC  = 1 << 2; // 4
const ADMIN = 1 << 3; // 8

このように書くと、それぞれのフラグが重ならないビットになります。

READ  = 0001
WRITE = 0010
EXEC  = 0100
ADMIN = 1000

手で 1248 と書くよりも、「何番目のビットを使っているか」がわかりやすくなります。

>>:右シフト

>> は、ビットを右にずらす演算子です。

>> の基本的な使い方

$a = 8; // 1000

echo $a >> 1; // 4

2進数で見ると、次のようになります。

1000  // 8
↓ 右に1つシフト
0100  // 4

正の整数では、右に1つシフトすると、おおむね2で割る動きになります。

echo 8 >> 1; // 4
echo 8 >> 2; // 2
echo 8 >> 3; // 1

ただし、>> は通常の割り算と完全に同じではありません。

PHPの右シフトは算術シフトであり、負の数を右シフトした場合、符号ビットが保持されます。

echo -8 >> 1; // -4

正の整数を扱う場合は「2で割るような動き」と理解して問題ありませんが、負の数や割り算目的で使う場合は注意が必要です。

実務では、単純に割り算したいなら /intdiv() を使うほうが読みやすいです。

ビット演算子と論理演算子の違い

PHPでは、ビット演算子と論理演算子を混同しないことが重要です。

種類演算子対象
ビット演算子&, `, ^, ~`
論理演算子&&, `

&&& の違い

&& は論理ANDです。

$a = true;
$b = false;

var_dump($a && $b); // bool(false)

一方、& はビットANDです。

$a = 5; // 0101
$b = 3; // 0011

var_dump($a & $b); // int(1)

次の例を見ると、違いがわかりやすいです。

$a = 1;
$b = 2;

var_dump($a && $b); // bool(true)
var_dump($a & $b);  // int(0)

$a && $b は、12 もPHPでは真として扱われるため、結果は true になります。

一方、$a & $b はビット単位で比較します。

  0001  // 1
& 0010  // 2
------
  0000  // 0

そのため、結果は 0 になります。

||| の違い

| はビットOR、|| は論理ORです。

ビットフラグを合成したい場合は、| を使います。

const READ  = 1;
const WRITE = 2;

$permission = READ | WRITE;

echo $permission; // 3

一方、|| を使うと論理演算になるため、結果は真偽値になります。

var_dump(READ || WRITE); // bool(true)

ビットフラグを扱う場合、||| の違いは非常に重要です。

ビット演算子の代入演算子

PHPには、ビット演算子と代入を組み合わせた演算子もあります。

演算子意味
&=ビットANDして代入
`=`
^=ビットXORして代入
<<=左シフトして代入
>>=右シフトして代入

&= の例

$a = 5;
$a &= 3;

echo $a; // 1

これは、次のコードと同じです。

$a = $a & 3;

フラグ操作での代入演算子

ビットフラグを操作するときは、代入演算子を使うとコードが短くなります。

const READ  = 1 << 0; // 1
const WRITE = 1 << 1; // 2
const EXEC  = 1 << 2; // 4

$permission = 0;

// READを追加
$permission |= READ;

// WRITEを追加
$permission |= WRITE;

// WRITEを外す
$permission &= ~WRITE;

// EXECを切り替える
$permission ^= EXEC;

このように、ビット演算子はフラグ管理と非常に相性がよいです。

ビットフラグとは

ビット演算子の代表的な使い道が、ビットフラグです。

ビットフラグとは、複数のON/OFF状態を1つの整数にまとめて管理する方法です。

たとえば、ユーザー権限を次のように定義できます。

const PERMISSION_READ   = 1; // 0001
const PERMISSION_WRITE  = 2; // 0010
const PERMISSION_DELETE = 4; // 0100
const PERMISSION_ADMIN  = 8; // 1000

または、左シフトを使って次のように書けます。

const PERMISSION_READ   = 1 << 0; // 1
const PERMISSION_WRITE  = 1 << 1; // 2
const PERMISSION_DELETE = 1 << 2; // 4
const PERMISSION_ADMIN  = 1 << 3; // 8

複数の権限をまとめる

ユーザーに「読み取り」と「書き込み」の権限を与える場合は、次のように書けます。

$permission = PERMISSION_READ | PERMISSION_WRITE;

ビットで見ると、次のようになります。

READ  = 0001
WRITE = 0010

0001 | 0010 = 0011

つまり、$permission の値は 3 になります。

この 3 という1つの整数の中に、「読み取り権限」と「書き込み権限」の両方が入っていることになります。

権限を持っているか確認する

特定の権限を持っているか確認するには、& を使います。

if (($permission & PERMISSION_READ) !== 0) {
    echo '読み取り権限があります';
}

より厳密に書くなら、次のようにします。

if (($permission & PERMISSION_READ) === PERMISSION_READ) {
    echo '読み取り権限があります';
}

複数の権限をまとめて確認する場合は、次の書き方が安全です。

$required = PERMISSION_READ | PERMISSION_WRITE;

if (($permission & $required) === $required) {
    echo '必要な権限をすべて持っています';
}

この書き方なら、READWRITE の両方を持っているか確認できます。

権限を追加する

権限を追加するには、| を使います。

$permission |= PERMISSION_DELETE;

これは、次のコードと同じ意味です。

$permission = $permission | PERMISSION_DELETE;

権限を削除する

権限を削除するには、&~ を組み合わせます。

$permission &= ~PERMISSION_WRITE;

これは、PERMISSION_WRITE のビットだけを外す処理です。

権限を切り替える

権限をON/OFFで切り替えるには、^ を使います。

$permission ^= PERMISSION_ADMIN;

PERMISSION_ADMIN がOFFならONになり、ONならOFFになります。

実用的なビットフラグのサンプルコード

次は、ビットフラグを使って権限管理を行うサンプルです。

<?php

const READ   = 1 << 0; // 1
const WRITE  = 1 << 1; // 2
const DELETE = 1 << 2; // 4
const ADMIN  = 1 << 3; // 8

function hasPermission(int $permissions, int $permission): bool
{
    return ($permissions & $permission) === $permission;
}

function addPermission(int $permissions, int $permission): int
{
    return $permissions | $permission;
}

function removePermission(int $permissions, int $permission): int
{
    return $permissions & ~$permission;
}

function togglePermission(int $permissions, int $permission): int
{
    return $permissions ^ $permission;
}

$permissions = 0;

$permissions = addPermission($permissions, READ);
$permissions = addPermission($permissions, WRITE);

var_dump($permissions); // int(3)

var_dump(hasPermission($permissions, READ));   // bool(true)
var_dump(hasPermission($permissions, DELETE)); // bool(false)

$permissions = removePermission($permissions, WRITE);

var_dump(hasPermission($permissions, WRITE)); // bool(false)

$permissions = togglePermission($permissions, ADMIN);

var_dump(hasPermission($permissions, ADMIN)); // bool(true)

このように、ビットフラグを使うと、複数の権限や設定を1つの整数で管理できます。

文字列に対するビット演算

PHPでは、整数だけでなく、文字列に対してビット演算を行うこともできます。

ただし、通常のWebアプリケーション開発ではあまり使われません。

文字列同士のビット演算

両方のオペランドが文字列の場合、PHPは各文字のASCII値に対してビット演算を行い、結果も文字列になります。

たとえば、次のコードを見てください。

echo "A" | " "; // "a"

これは、ASCIIコード上で "A" とスペースをビットORすると、小文字の "a" になるためです。

"A" = 01000001
" " = 00100000
---------------
"a" = 01100001

文字列に対する ~

~ も文字列に対して使えます。

$result = ~"A";

この場合も、文字列の各文字に対してビット反転が行われます。

ただし、結果は人間が読める文字になるとは限りません。

可読性も低いため、通常のアプリケーション開発では積極的に使う場面は少ないです。

ビット演算子の優先順位に注意する

ビット演算子を使うときは、演算子の優先順位に注意が必要です。

特に、ビット演算子と比較演算子を同じ式の中で使う場合は、括弧を付ける習慣を持つと安全です。

避けたほうがよい書き方

if ($permission & READ === READ) {
    echo '読み取り可能';
}

この書き方は、読み手が評価順序を誤解しやすくなります。

安全な書き方

if (($permission & READ) === READ) {
    echo '読み取り可能';
}

また、単一フラグの有無を確認するだけなら、次のように書くこともできます。

if (($permission & READ) !== 0) {
    echo '読み取り可能';
}

ビット演算と比較演算を組み合わせるときは、基本的に括弧を付けると覚えておくとよいです。

PHPでビット演算子を使うメリット

PHPでビット演算子を使うメリットは、主に次の3つです。

複数の状態を1つの整数で管理できる

ビット演算子を使うと、複数のON/OFF状態を1つの整数にまとめられます。

たとえば、次のように個別の真偽値で管理することもできます。

$canRead = true;
$canWrite = true;
$canDelete = false;
$isAdmin = false;

しかし、ビットフラグを使えば、次のように1つの変数で管理できます。

$permission = READ | WRITE;

権限や設定のように、複数の状態が同時にONになりうる場合に便利です。

データベースに保存しやすい

ビットフラグを使うと、複数の状態を1つの整数カラムに保存できます。

permissions = 3

この 3 は、次の状態を表します。

READ + WRITE

ただし、数値だけを見ても意味がわかりづらいため、必ず定数名やコメントを用意することが重要です。

状態の追加・削除が簡潔に書ける

ビット演算子を使うと、状態の追加・削除・確認を短く書けます。

$flags |= READ;     // READを追加
$flags &= ~WRITE;   // WRITEを削除
$flags ^= ADMIN;    // ADMINを切り替え

このような処理は、ビットフラグと非常に相性がよいです。

PHPでビット演算子を使うデメリット

ビット演算子は便利ですが、使いどころを間違えるとコードが読みにくくなります。

可読性が下がりやすい

次のようなコードは、意味がわかりにくいです。

if ($user->flags & 8) {
    // ...
}

この 8 が何を表しているのか、コードだけではわかりません。

そのため、ビットフラグを使う場合は、必ず定数を使いましょう。

if (($user->flags & UserFlag::ADMIN) === UserFlag::ADMIN) {
    // ...
}

定数名を使うことで、コードの意図が明確になります。

フラグ値の重複に注意が必要

ビットフラグでは、各フラグの値を重複させてはいけません。

悪い例は次のとおりです。

const READ  = 1;
const WRITE = 2;
const ADMIN = 2; // WRITEと重複している

この場合、WRITEADMIN を区別できなくなります。

良い例は次のとおりです。

const READ  = 1 << 0; // 1
const WRITE = 1 << 1; // 2
const ADMIN = 1 << 2; // 4

各フラグは、必ず 124816 のように、2の累乗で定義します。

データベース検索がわかりにくくなる

ビットフラグをデータベースに保存すると、検索条件が少しわかりにくくなることがあります。

たとえば、MySQLで特定のフラグを持つユーザーを検索する場合は、次のように書きます。

SELECT *
FROM users
WHERE (permissions & 4) = 4;

単一フラグの有無を見るだけなら、次のように書くこともあります。

SELECT *
FROM users
WHERE (permissions & 4) <> 0;

このように、通常のカラム検索よりも直感的ではありません。

権限や状態が複雑な場合は、無理にビットフラグを使わず、別テーブルやenumで管理したほうがよいケースもあります。

PHPでビット演算子が向いている場面

ビット演算子は、どのような場面にも向いているわけではありません。

特に向いているのは、複数の状態が同時にONになりうる場面です。

権限管理

ユーザー権限は、ビット演算子と相性がよい代表例です。

const VIEW   = 1 << 0;
const EDIT   = 1 << 1;
const DELETE = 1 << 2;

ユーザーが複数の権限を同時に持つ場合、ビットフラグで管理しやすくなります。

機能フラグ

プランやユーザーごとに利用できる機能を管理する場合にも使えます。

const FEATURE_SEARCH = 1 << 0;
const FEATURE_EXPORT = 1 << 1;
const FEATURE_BETA   = 1 << 2;

$features = FEATURE_SEARCH | FEATURE_BETA;

この例では、検索機能とベータ機能がONになっています。

エラー設定や外部APIのオプション指定

PHPの標準機能や外部ライブラリでも、複数のオプションを | で組み合わせる設計が使われることがあります。

たとえば、エラー報告レベルの指定では、次のような書き方があります。

error_reporting(E_ERROR | E_WARNING | E_PARSE);

また、特定のエラーだけを除外したい場合は、次のように書けます。

error_reporting(E_ALL & ~E_NOTICE);

これは、「すべてのエラーから E_NOTICE を除外する」という意味です。

PHPでビット演算子が向いていない場面

ビット演算子は便利ですが、何でもビットフラグにすればよいわけではありません。

1つしか選ばれないステータス管理

たとえば、注文ステータスのように、常に1つの状態しか取らないものには、ビットフラグはあまり向いていません。

$status = 'pending';
$status = 'paid';
$status = 'shipped';
$status = 'cancelled';

このような場合は、文字列、定数、enumなどで管理するほうが自然です。

ビットフラグが向いているのは、次のような状態です。

読み取り権限:ON
書き込み権限:ON
削除権限:OFF
管理者権限:OFF

つまり、複数の状態が同時に成立するケースです。

意味を説明しづらい設定

ビットフラグは、数値だけを見ると意味がわかりにくいという弱点があります。

たとえば、データベースに次のような値が入っていたとします。

permissions = 13

これだけを見ても、何の権限を持っているのかすぐにはわかりません。

13 は2進数で 1101 なので、たとえば次のような意味になります。

READ  = ON
WRITE = OFF
DELETE = ON
ADMIN = ON

このように、ビットフラグは便利な一方で、読み解くにはルールを知っている必要があります。

チーム開発では、定数名、コメント、設計ドキュメントを用意しておくことが大切です。

PHPのビット演算子でよくあるミス

ビット演算子を使うときは、いくつかのミスに注意が必要です。

&&& を間違える

ビットANDは &、論理ANDは && です。

if ($a & $b) {
    // ビットAND
}
if ($a && $b) {
    // 論理AND
}

条件分岐で普通に真偽値を判定したい場合は、&& を使います。

ビットフラグを確認したい場合は、& を使います。

||| を間違える

ビットORは |、論理ORは || です。

ビットフラグを合成したい場合は、次のように書きます。

$flags = READ | WRITE;

次のように書くと、意図した結果になりません。

$flags = READ || WRITE;

|| は論理ORなので、結果は true または false になります。

括弧を省略する

ビット演算子と比較演算子を混ぜる場合は、括弧を付けましょう。

避けたほうがよい書き方は次のとおりです。

if ($flags & READ === READ) {
    echo 'READあり';
}

安全な書き方は次のとおりです。

if (($flags & READ) === READ) {
    echo 'READあり';
}

または、単一フラグの有無を確認するなら、次のように書けます。

if (($flags & READ) !== 0) {
    echo 'READあり';
}

フラグ値を連番にしてしまう

ビットフラグでは、フラグ値を 123 のような連番にしてはいけません。

悪い例は次のとおりです。

const READ  = 1;
const WRITE = 2;
const EXEC  = 3;

EXEC = 3 は、2進数で 0011 です。

つまり、READWRITE を組み合わせた値と同じになってしまいます。

READ  = 0001
WRITE = 0010
EXEC  = 0011

正しくは、次のように2の累乗で定義します。

const READ  = 1; // 0001
const WRITE = 2; // 0010
const EXEC  = 4; // 0100

または、左シフトを使って次のように書きます。

const READ  = 1 << 0;
const WRITE = 1 << 1;
const EXEC  = 1 << 2;

PHPのビット演算子の覚え方

ビット演算子は、次のように覚えると整理しやすいです。

演算子覚え方
&両方ONならON
``
^違っていたらON
~ON/OFFを反転
<<左にずらす。正の整数ではおおむね2倍
>>右にずらす。正の整数ではおおむね2分の1

特に実務でよく使うのは、次の3つです。

&  // フラグを持っているか確認する
|  // フラグを追加・合成する
~  // フラグを外すときに使う

さらに、切り替えには ^ を使います。

^  // フラグをON/OFFで切り替える

PHPのビット演算子まとめ

PHPのビット演算子は、整数を2進数のビット列として扱い、ビット単位で計算するための演算子です。

主な演算子は、次の6つです。

演算子意味
&両方のビットが 1 の場合だけ 1
``
^片方だけが 1 の場合に 1
~ビットを反転する
<<ビットを左にずらす
>>ビットを右にずらす

実務では、特にビットフラグとして使われることが多いです。

const READ  = 1 << 0;
const WRITE = 1 << 1;
const EXEC  = 1 << 2;

$permission = READ | WRITE;

if (($permission & READ) === READ) {
    echo '読み取り可能';
}

$permission |= EXEC;   // EXECを追加
$permission &= ~WRITE; // WRITEを削除
$permission ^= READ;   // READを切り替え

ビット演算子を使う最大のメリットは、複数のON/OFF状態を1つの整数で管理できることです。

ただし、ビットフラグは数値だけを見ると意味がわかりにくいため、定数名を使い、括弧を付けて、読みやすく書くことが重要です。

PHPでビット演算子を使うときは、次のポイントを押さえておきましょう。

// フラグを追加
$flags |= READ;

// フラグを確認
if (($flags & READ) !== 0) {
    // READあり
}

// 複数フラグをすべて持っているか確認
if (($flags & (READ | WRITE)) === (READ | WRITE)) {
    // READとWRITEの両方あり
}

// フラグを削除
$flags &= ~WRITE;

// フラグを切り替え
$flags ^= ADMIN;

ビット演算子は、毎日のPHP開発で頻繁に使うものではないかもしれません。

しかし、権限管理、設定フラグ、エラー設定、ライブラリのオプション指定などを理解するうえで重要な知識です。

特に、&|~ の3つを押さえておくと、PHPのビットフラグをかなり読みやすくなります。

以上、PHPのビット演算子についてでした。

最後までお読みいただき、ありがとうございました。

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