PHPのお勉強!

PHP TOP

ビット演算子

ビット演算子は、整数値の特定のビットの評価や操作を行います。

ビット演算子
名前 結果
$a & $b ビット積 $a および $b の両方にセットされているビットがセットされます
$a | $b ビット和 $a または $b のどちらかにセットされているビットがセットされます
$a ^ $b 排他的論理和 $a または $b のどちらか一方にセットされており、両方にセットされていないビットがセットされます
~ $a 否定 $a にセットされているビットはセットせず、そうでないものは逆にします
$a << $b 左シフト $a のビットを左に $b ビットシフトする (各シフトは "2をかける" ことを意味します)
$a >> $b 右シフト $a のビットを右に $b ビットシフトします (各シフトは "2で割る" ことを意味します)

PHP のシフト処理は算術シフトです。両端からあふれたビットは捨てられます。 左シフトをすると右側にはゼロが埋められます。 符号ビットは左端からあふれて消えます。 つまり、オペランドの符号は維持されないということです。 右シフトの際には、符号ビットと同じ内容が左端から埋められます。 つまり、この場合はオペランドの符号が維持されます。

括弧を使うことで、望みどおりの 優先順位 で処理させることができます。たとえば、$a & $b == true はまず等価かどうかを評価してからビット演算を行いますが ($a & $b) == true はまずビット演算を行ってから等価かどうかを評価します。

&| そして ^ 演算子の左右のオペランドが文字列の場合、その演算は、 文字列を構成する文字の ASCII 値を使って行います。その結果は文字列になります。 それ以外の場合は、左右のオペランドを integer に変換 し、結果も integer になります。

~ 演算子のオペランドが文字列の場合、その演算は、 文字列を構成する文字の ASCII 値を使って行います。その結果は文字列になります。 それ以外の場合は、オペランドや演算結果を integer として扱います。

<< および >> 演算子のオペランドとその結果は、常に integer として扱います。

PHP の ini 設定 error_reporting はビット値を用いています。
これを用いて、特定のビットを落とす演算の例を見てみましょう。
notice 以外のすべてのエラーを表示させるには、
php.ini ファイルで
E_ALL & ~E_NOTICE
と指定することになります。
    

まずは E_ALL。
00000000000000000111011111111111
そして E_NOTICE...。
00000000000000000000000000001000
... これを ~ で逆転させます。
11111111111111111111111111110111
最後に AND (&) を使い、両方ともビットが立っているところをみつけます。
00000000000000000111011111110111
    

同じ結果を得るもうひとつの方法として、 XOR (^)
を使ってどちらか一方だけ立っているビットを探すという方法もあります。
E_ALL ^ E_NOTICE
    

error_reporting を使って、特定のビットを立てる処理の例を見てみましょう。
通常のエラーとリカバー可能なエラーだけを表示させるには、次のようにします。
E_ERROR | E_RECOVERABLE_ERROR
    

この処理は、 E_ERROR
0000000000000000000000000000000100000000000000000001000000000000
を OR (|) 演算子でつないで、
少なくともどちらかのビットが立っているところを取得します。
00000000000000000001000000000001
    

例1 整数値におけるビット AND、OR および XOR 演算

<?php
/*
* このへんは無視してください。
* たんに結果をきれいに表示させるためだけのものです。
*/

$format = '(%1$2d = %1$04b) = (%2$2d = %2$04b)'
. ' %3$s (%4$2d = %4$04b)' . "\n";

echo <<<EOH
--------- --------- -- ---------
result value op test
--------- --------- -- ---------
EOH;


/*
* ここからが本番
*/

$values = array(0, 1, 2, 4, 8);
$test = 1 + 4;

echo
"\n Bitwise AND \n";
foreach (
$values as $value) {
$result = $value & $test;
printf($format, $result, $value, '&', $test);
}

echo
"\n Bitwise Inclusive OR \n";
foreach (
$values as $value) {
$result = $value | $test;
printf($format, $result, $value, '|', $test);
}

echo
"\n Bitwise Exclusive OR (XOR) \n";
foreach (
$values as $value) {
$result = $value ^ $test;
printf($format, $result, $value, '^', $test);
}
?>

上の例の出力は以下となります。

 ---------     ---------  -- ---------
 result        value      op test
 ---------     ---------  -- ---------
 Bitwise AND
( 0 = 0000) = ( 0 = 0000) & ( 5 = 0101)
( 1 = 0001) = ( 1 = 0001) & ( 5 = 0101)
( 0 = 0000) = ( 2 = 0010) & ( 5 = 0101)
( 4 = 0100) = ( 4 = 0100) & ( 5 = 0101)
( 0 = 0000) = ( 8 = 1000) & ( 5 = 0101)

 Bitwise Inclusive OR
( 5 = 0101) = ( 0 = 0000) | ( 5 = 0101)
( 5 = 0101) = ( 1 = 0001) | ( 5 = 0101)
( 7 = 0111) = ( 2 = 0010) | ( 5 = 0101)
( 5 = 0101) = ( 4 = 0100) | ( 5 = 0101)
(13 = 1101) = ( 8 = 1000) | ( 5 = 0101)

 Bitwise Exclusive OR (XOR)
( 5 = 0101) = ( 0 = 0000) ^ ( 5 = 0101)
( 4 = 0100) = ( 1 = 0001) ^ ( 5 = 0101)
( 7 = 0111) = ( 2 = 0010) ^ ( 5 = 0101)
( 1 = 0001) = ( 4 = 0100) ^ ( 5 = 0101)
(13 = 1101) = ( 8 = 1000) ^ ( 5 = 0101)

例2 文字列でのビット XOR 演算

<?php
echo 12 ^ 9; // 出力は '5'

echo "12" ^ "9"; // 出力はバックスペース文字 (ascii 8)
// ('1' (ascii 49)) ^ ('9' (ascii 57)) = #8

echo "hallo" ^ "hello"; // 出力は、ascii コード #0 #4 #0 #0 #0
// 'a' ^ 'e' = #4

echo 2 ^ "3"; // 出力は 1
// 2 ^ ((int)"3") == 1

echo "2" ^ 3; // 出力は 1
// ((int)"2") ^ 3 == 1
?>

例3 整数値のビットシフト

<?php
/*
* これが例です
*/

echo "\n--- BIT SHIFT RIGHT ON POSITIVE INTEGERS ---\n";

$val = 4;
$places = 1;
$res = $val >> $places;
p($res, $val, '>>', $places, 'copy of sign bit shifted into left side');

$val = 4;
$places = 2;
$res = $val >> $places;
p($res, $val, '>>', $places);

$val = 4;
$places = 3;
$res = $val >> $places;
p($res, $val, '>>', $places, 'bits shift out right side');

$val = 4;
$places = 4;
$res = $val >> $places;
p($res, $val, '>>', $places, 'same result as above; can not shift beyond 0');


echo
"\n--- BIT SHIFT RIGHT ON NEGATIVE INTEGERS ---\n";

$val = -4;
$places = 1;
$res = $val >> $places;
p($res, $val, '>>', $places, 'copy of sign bit shifted into left side');

$val = -4;
$places = 2;
$res = $val >> $places;
p($res, $val, '>>', $places, 'bits shift out right side');

$val = -4;
$places = 3;
$res = $val >> $places;
p($res, $val, '>>', $places, 'same result as above; can not shift beyond -1');


echo
"\n--- BIT SHIFT LEFT ON POSITIVE INTEGERS ---\n";

$val = 4;
$places = 1;
$res = $val << $places;
p($res, $val, '<<', $places, 'zeros fill in right side');

$val = 4;
$places = (PHP_INT_SIZE * 8) - 4;
$res = $val << $places;
p($res, $val, '<<', $places);

$val = 4;
$places = (PHP_INT_SIZE * 8) - 3;
$res = $val << $places;
p($res, $val, '<<', $places, 'sign bits get shifted out');

$val = 4;
$places = (PHP_INT_SIZE * 8) - 2;
$res = $val << $places;
p($res, $val, '<<', $places, 'bits shift out left side');


echo
"\n--- BIT SHIFT LEFT ON NEGATIVE INTEGERS ---\n";

$val = -4;
$places = 1;
$res = $val << $places;
p($res, $val, '<<', $places, 'zeros fill in right side');

$val = -4;
$places = (PHP_INT_SIZE * 8) - 3;
$res = $val << $places;
p($res, $val, '<<', $places);

$val = -4;
$places = (PHP_INT_SIZE * 8) - 2;
$res = $val << $places;
p($res, $val, '<<', $places, 'bits shift out left side, including sign bit');


/*
* このへんは無視してください。
* たんに結果をきれいに表示させるためだけのものです。
*/

function p($res, $val, $op, $places, $note = '') {
$format = '%0' . (PHP_INT_SIZE * 8) . "b\n";

printf("Expression: %d = %d %s %d\n", $res, $val, $op, $places);

echo
" Decimal:\n";
printf(" val=%d\n", $val);
printf(" res=%d\n", $res);

echo
" Binary:\n";
printf(' val=' . $format, $val);
printf(' res=' . $format, $res);

if (
$note) {
echo
" NOTE: $note\n";
}

echo
"\n";
}
?>

上の例の 32 ビットマシンでの出力は、このようになります。


--- BIT SHIFT RIGHT ON POSITIVE INTEGERS ---
Expression: 2 = 4 >> 1
 Decimal:
val=4
res=2
 Binary:
val=00000000000000000000000000000100
res=00000000000000000000000000000010
 NOTE: copy of sign bit shifted into left side

Expression: 1 = 4 >> 2
 Decimal:
val=4
res=1
 Binary:
val=00000000000000000000000000000100
res=00000000000000000000000000000001

Expression: 0 = 4 >> 3
 Decimal:
val=4
res=0
 Binary:
val=00000000000000000000000000000100
res=00000000000000000000000000000000
 NOTE: bits shift out right side

Expression: 0 = 4 >> 4
 Decimal:
val=4
res=0
 Binary:
val=00000000000000000000000000000100
res=00000000000000000000000000000000
 NOTE: same result as above; can not shift beyond 0


--- BIT SHIFT RIGHT ON NEGATIVE INTEGERS ---
Expression: -2 = -4 >> 1
 Decimal:
val=-4
res=-2
 Binary:
val=11111111111111111111111111111100
res=11111111111111111111111111111110
 NOTE: copy of sign bit shifted into left side

Expression: -1 = -4 >> 2
 Decimal:
val=-4
res=-1
 Binary:
val=11111111111111111111111111111100
res=11111111111111111111111111111111
 NOTE: bits shift out right side

Expression: -1 = -4 >> 3
 Decimal:
val=-4
res=-1
 Binary:
val=11111111111111111111111111111100
res=11111111111111111111111111111111
 NOTE: same result as above; can not shift beyond -1


--- BIT SHIFT LEFT ON POSITIVE INTEGERS ---
Expression: 8 = 4 << 1
 Decimal:
val=4
res=8
 Binary:
val=00000000000000000000000000000100
res=00000000000000000000000000001000
 NOTE: zeros fill in right side

Expression: 1073741824 = 4 << 28
 Decimal:
val=4
res=1073741824
 Binary:
val=00000000000000000000000000000100
res=01000000000000000000000000000000

Expression: -2147483648 = 4 << 29
 Decimal:
val=4
res=-2147483648
 Binary:
val=00000000000000000000000000000100
res=10000000000000000000000000000000
 NOTE: sign bits get shifted out

Expression: 0 = 4 << 30
 Decimal:
val=4
res=0
 Binary:
val=00000000000000000000000000000100
res=00000000000000000000000000000000
 NOTE: bits shift out left side


--- BIT SHIFT LEFT ON NEGATIVE INTEGERS ---
Expression: -8 = -4 << 1
 Decimal:
val=-4
res=-8
 Binary:
val=11111111111111111111111111111100
res=11111111111111111111111111111000
 NOTE: zeros fill in right side

Expression: -2147483648 = -4 << 29
 Decimal:
val=-4
res=-2147483648
 Binary:
val=11111111111111111111111111111100
res=10000000000000000000000000000000

Expression: 0 = -4 << 30
 Decimal:
val=-4
res=0
 Binary:
val=11111111111111111111111111111100
res=00000000000000000000000000000000
 NOTE: bits shift out left side, including sign bit

上の例の 64 ビットマシンでの出力は、このようになります。


--- BIT SHIFT RIGHT ON POSITIVE INTEGERS ---
Expression: 2 = 4 >> 1
 Decimal:
val=4
res=2
 Binary:
val=0000000000000000000000000000000000000000000000000000000000000100
res=0000000000000000000000000000000000000000000000000000000000000010
 NOTE: copy of sign bit shifted into left side

Expression: 1 = 4 >> 2
 Decimal:
val=4
res=1
 Binary:
val=0000000000000000000000000000000000000000000000000000000000000100
res=0000000000000000000000000000000000000000000000000000000000000001

Expression: 0 = 4 >> 3
 Decimal:
val=4
res=0
 Binary:
val=0000000000000000000000000000000000000000000000000000000000000100
res=0000000000000000000000000000000000000000000000000000000000000000
 NOTE: bits shift out right side

Expression: 0 = 4 >> 4
 Decimal:
val=4
res=0
 Binary:
val=0000000000000000000000000000000000000000000000000000000000000100
res=0000000000000000000000000000000000000000000000000000000000000000
 NOTE: same result as above; can not shift beyond 0


--- BIT SHIFT RIGHT ON NEGATIVE INTEGERS ---
Expression: -2 = -4 >> 1
 Decimal:
val=-4
res=-2
 Binary:
val=1111111111111111111111111111111111111111111111111111111111111100
res=1111111111111111111111111111111111111111111111111111111111111110
 NOTE: copy of sign bit shifted into left side

Expression: -1 = -4 >> 2
 Decimal:
val=-4
res=-1
 Binary:
val=1111111111111111111111111111111111111111111111111111111111111100
res=1111111111111111111111111111111111111111111111111111111111111111
 NOTE: bits shift out right side

Expression: -1 = -4 >> 3
 Decimal:
val=-4
res=-1
 Binary:
val=1111111111111111111111111111111111111111111111111111111111111100
res=1111111111111111111111111111111111111111111111111111111111111111
 NOTE: same result as above; can not shift beyond -1


--- BIT SHIFT LEFT ON POSITIVE INTEGERS ---
Expression: 8 = 4 << 1
 Decimal:
val=4
res=8
 Binary:
val=0000000000000000000000000000000000000000000000000000000000000100
res=0000000000000000000000000000000000000000000000000000000000001000
 NOTE: zeros fill in right side

Expression: 4611686018427387904 = 4 << 60
 Decimal:
val=4
res=4611686018427387904
 Binary:
val=0000000000000000000000000000000000000000000000000000000000000100
res=0100000000000000000000000000000000000000000000000000000000000000

Expression: -9223372036854775808 = 4 << 61
 Decimal:
val=4
res=-9223372036854775808
 Binary:
val=0000000000000000000000000000000000000000000000000000000000000100
res=1000000000000000000000000000000000000000000000000000000000000000
 NOTE: sign bits get shifted out

Expression: 0 = 4 << 62
 Decimal:
val=4
res=0
 Binary:
val=0000000000000000000000000000000000000000000000000000000000000100
res=0000000000000000000000000000000000000000000000000000000000000000
 NOTE: bits shift out left side


--- BIT SHIFT LEFT ON NEGATIVE INTEGERS ---
Expression: -8 = -4 << 1
 Decimal:
val=-4
res=-8
 Binary:
val=1111111111111111111111111111111111111111111111111111111111111100
res=1111111111111111111111111111111111111111111111111111111111111000
 NOTE: zeros fill in right side

Expression: -9223372036854775808 = -4 << 61
 Decimal:
val=-4
res=-9223372036854775808
 Binary:
val=1111111111111111111111111111111111111111111111111111111111111100
res=1000000000000000000000000000000000000000000000000000000000000000

Expression: 0 = -4 << 62
 Decimal:
val=-4
res=0
 Binary:
val=1111111111111111111111111111111111111111111111111111111111111100
res=0000000000000000000000000000000000000000000000000000000000000000
 NOTE: bits shift out left side, including sign bit

警告

PHP_INT_MAX を超える数のビット演算には、 gmp 拡張モジュールの関数を使いましょう。

add a note

User Contributed Notes 27 notes

up
116
wbcarts at juno dot com
12 years ago
BITWISE FLAGS for Custom PHP Objects

Sometimes I need a custom PHP Object that holds several boolean TRUE or FALSE values. I could easily include a variable for each of them, but as always, code has a way to get unweildy pretty fast. A more intelligent approach always seems to be the answer, even if it seems to be overkill at first.

I start with an abstract base class which will hold a single integer variable called $flags. This simple integer can hold 32 TRUE or FALSE boolean values. Another thing to consider is to just set certain BIT values without disturbing any of the other BITS -- so included in the class definition is the setFlag($flag, $value) function, which will set only the chosen bit. Here's the abstract base class definition:

<?php

# BitwiseFlag.php

abstract class BitwiseFlag
{
protected
$flags;

/*
* Note: these functions are protected to prevent outside code
* from falsely setting BITS. See how the extending class 'User'
* handles this.
*
*/
protected function isFlagSet($flag)
{
return ((
$this->flags & $flag) == $flag);
}

protected function
setFlag($flag, $value)
{
if(
$value)
{
$this->flags |= $flag;
}
else
{
$this->flags &= ~$flag;
}
}
}

?>

The class above is abstract and cannot be instantiated, so an extension is required. Below is a simple extension called User -- which is severely truncated for clarity. Notice I am defining const variables AND methods to use them.

<?php

# User.php

require('BitwiseFlag.php');

class
User extends BitwiseFlag
{
const
FLAG_REGISTERED = 1; // BIT #1 of $flags has the value 1
const FLAG_ACTIVE = 2; // BIT #2 of $flags has the value 2
const FLAG_MEMBER = 4; // BIT #3 of $flags has the value 4
const FLAG_ADMIN = 8; // BIT #4 of $flags has the value 8

public function isRegistered(){
return
$this->isFlagSet(self::FLAG_REGISTERED);
}

public function
isActive(){
return
$this->isFlagSet(self::FLAG_ACTIVE);
}

public function
isMember(){
return
$this->isFlagSet(self::FLAG_MEMBER);
}

public function
isAdmin(){
return
$this->isFlagSet(self::FLAG_ADMIN);
}

public function
setRegistered($value){
$this->setFlag(self::FLAG_REGISTERED, $value);
}

public function
setActive($value){
$this->setFlag(self::FLAG_ACTIVE, $value);
}

public function
setMember($value){
$this->setFlag(self::FLAG_MEMBER, $value);
}

public function
setAdmin($value){
$this->setFlag(self::FLAG_ADMIN, $value);
}

public function
__toString(){
return
'User [' .
(
$this->isRegistered() ? 'REGISTERED' : '') .
(
$this->isActive() ? ' ACTIVE' : '') .
(
$this->isMember() ? ' MEMBER' : '') .
(
$this->isAdmin() ? ' ADMIN' : '') .
']';
}
}

?>

This seems like a lot of work, but we have addressed many issues, for example, using and maintaining the code is easy, and the getting and setting of flag values make sense. With the User class, you can now see how easy and intuitive bitwise flag operations become.

<?php

require('User.php')

$user = new User();
$user->setRegistered(true);
$user->setActive(true);
$user->setMember(true);
$user->setAdmin(true);

echo
$user; // outputs: User [REGISTERED ACTIVE MEMBER ADMIN]

?>
up
39
grayda dot NOSPAM at DONTSPAM dot solidinc dot org
15 years ago
Initially, I found bitmasking to be a confusing concept and found no use for it. So I've whipped up this code snippet in case anyone else is confused:

<?php

// The various details a vehicle can have
$hasFourWheels = 1;
$hasTwoWheels = 2;
$hasDoors = 4;
$hasRedColour = 8;

$bike = $hasTwoWheels;
$golfBuggy = $hasFourWheels;
$ford = $hasFourWheels | $hasDoors;
$ferrari = $hasFourWheels | $hasDoors | $hasRedColour;

$isBike = $hasFourWheels & $bike; # False, because $bike doens't have four wheels
$isGolfBuggy = $hasFourWheels & $golfBuggy; # True, because $golfBuggy has four wheels
$isFord = $hasFourWheels & $ford; # True, because $ford $hasFourWheels

?>

And you can apply this to a lot of things, for example, security:

<?php

// Security permissions:
$writePost = 1;
$readPost = 2;
$deletePost = 4;
$addUser = 8;
$deleteUser = 16;

// User groups:
$administrator = $writePost | $readPosts | $deletePosts | $addUser | $deleteUser;
$moderator = $readPost | $deletePost | $deleteUser;
$writer = $writePost | $readPost;
$guest = $readPost;

// function to check for permission
function checkPermission($user, $permission) {
if(
$user & $permission) {
return
true;
} else {
return
false;
}
}

// Now we apply all of this!
if(checkPermission($administrator, $deleteUser)) {
deleteUser("Some User"); # This is executed because $administrator can $deleteUser
}

?>

Once you get your head around it, it's VERY useful! Just remember to raise each value by the power of two to avoid problems
up
17
S?b.
19 years ago
A bitwise operators practical case :

<?php
// We want to know the red, green and blue values of this color :
$color = 0xFEA946 ;

$red = $color >> 16 ;
$green = ($color & 0x00FF00) >> 8 ;
$blue = $color & 0x0000FF ;

printf('Red : %X (%d), Green : %X (%d), Blue : %X (%d)',
$red, $red, $green, $green, $blue, $blue) ;

// Will display...
// Red : FE (254), Green : A9 (169), Blue : 46 (70)
?>
up
14
frankemeks77 at yahoo dot com
12 years ago
Just learning Bitwise Shift Operators.

The easiest way to resolve a bitwise shift operators is my multiply or dividing each step by two for left shift or right shift respectively

Example:

LEFT SHIFT
<?php echo 8 << 3; //64 ?>

//same as
<?php echo 8 * 2 * 2 * 2; ?>

RIGHT SHIFT
<?php echo 8 >> 3; //1 ?>

//same as
<?php echo ((8/2)/2)/2; //1 ?>

//Solving on a paper 8/2 = 4/2 = 2/2 = 1
up
11
Silver
15 years ago
Regarding what Bob said about flags, I'd like to point out there's a 100% safe way of defining flags, which is using hexadecimal notation for integers:

<?php
define
("f0", 0x1); // 2^0
define("f1", 0x2); // 2^1
define("f2", 0x4); // 2^2
define("f3", 0x8); // 2^3
define("f4", 0x10); // 2^4
define("f5", 0x20); // 2^5
// ...
define("f20", 0x1000000); // 2^20
define("f21", 0x2000000); // 2^21
define("f22", 0x4000000); // 2^22
define("f23", 0x8000000); // 2^23
define("f24", 0x10000000); // 2^24
// ... up to 2^31
?>

I always avoid using decimal notation when I have a large amount of different flags, because it's very easy to misspell numbers like 2^20 (1048576).
up
6
m0sh at hotmail dot com
16 years ago
@greenone - nice function, thanks. I've adapted it for key usage:

<?php
function bitxor($str, $key) {
$xorWidth = PHP_INT_SIZE*8;
// split
$o1 = str_split($str, $xorWidth);
$o2 = str_split(str_pad('', strlen($str), $key), $xorWidth);
$res = '';
$runs = count($o1);
for(
$i=0;$i<$runs;$i++)
$res .= decbin(bindec($o1[$i]) ^ bindec($o2[$i]));
return
$res;
}
?>
up
9
zewt at hotmail dot com
17 years ago
if you use bitwise you MUST make sure your variables are integers, otherwise you can get incorrect results.

I recommend ALWAYS

(int)$var & (int)$var2

This will save you many headaches when troubleshooting a completely illogical result.