PHP Conference Japan 2024

位元運算子

位元運算子允許評估和操作整數中的特定位元。

位元運算子
範例 名稱 結果
$a & $b And(位元與) $a$b 中都設定的位元會被設定。
$a | $b Or(位元或,包含或) $a$b 中有設定的位元會被設定。
$a ^ $b Xor(位元互斥或) $a$b 中有設定但不同時設定的位元會被設定。
~ $a Not(位元反相) $a 中設定的位元不會被設定,反之亦然。
$a << $b 左移 $a 的位元向左移動 $b 步(每一步表示「乘以二」)
$a >> $b 右移 $a 的位元向右移動 $b 步(每一步表示「除以二」)

PHP 中的位元移位是算術運算。從任一端移出的位元會被捨棄。左移會在右側移入零,而符號位會在左側移出,這表示運算元的符號不會保留。右移會在左側移入符號位的副本,這表示運算元的符號會保留。

請使用括號來確保想要的優先順序。例如,$a & $b == true 會先評估相等性,然後再進行位元與運算;而 ($a & $b) == true 則會先評估位元與運算,然後再進行相等性評估。

如果 &|^ 運算子的兩個運算元都是字串,則運算將在組成字串的字元的 ASCII 值上執行,結果會是一個字串。在所有其他情況下,兩個運算元都會被轉換為整數,結果會是一個整數。

如果 ~ 運算子的運算元是一個字串,則運算將在組成字串的字元的 ASCII 值上執行,結果會是一個字串,否則運算元和結果都將被視為整數。

<<>> 運算子的兩個運算元和結果都永遠會被視為整數。

     PHP's error_reporting ini setting uses bitwise values,
     providing a real-world demonstration of turning
     bits off. To show all errors, except for notices,
     the php.ini file instructions say to use:
     E_ALL & ~E_NOTICE
    

     This works by starting with E_ALL:
     00000000000000000111011111111111
     Then taking the value of E_NOTICE...
     00000000000000000000000000001000
     ... and inverting it via ~:
     11111111111111111111111111110111
     Finally, it uses AND (&) to find the bits turned
     on in both values:
     00000000000000000111011111110111
    

     Another way to accomplish that is using XOR (^)
     to find bits that are on in only one value or the other:
     E_ALL ^ E_NOTICE
    

     error_reporting can also be used to demonstrate turning bits on.
     The way to show just errors and recoverable errors is:
     E_ERROR | E_RECOVERABLE_ERROR
    

     This process combines E_ERROR
     00000000000000000000000000000001
     and
     00000000000000000001000000000000
     using the OR (|) operator
     to get the bits turned on in either value:
     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 位元 AND \n";
foreach (
$values as $value) {
$result = $value & $test;
printf($format, $result, $value, '&', $test);
}

echo
"\n 位元包含 OR \n";
foreach (
$values as $value) {
$result = $value | $test;
printf($format, $result, $value, '|', $test);
}

echo
"\n 位元互斥 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
/*
* Here are the examples.
*/

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');


/*
* Ignore this bottom section,
* it is just formatting to make output clearer.
*/

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

警告

請使用 gmp 擴充功能中的函式,以在超出 PHP_INT_MAX 的數字上進行位元操作。

新增註解

使用者貢獻的註解 27 個註解

117
wbcarts at juno dot com
12 年前
自訂 PHP 物件的 BITWISE 旗標

有時我需要一個自訂的 PHP 物件,該物件會持有幾個布林值 TRUE 或 FALSE。我可以輕鬆地為每個值包含一個變數,但一如既往,程式碼很快就會變得笨拙。更聰明的方法似乎總是答案,即使一開始看起來有點過頭。

我從一個抽象的基底類別開始,該類別將持有一個名為 $flags 的整數變數。這個簡單的整數可以持有 32 個 TRUE 或 FALSE 布林值。另一個要考慮的是只設定特定的 BIT 值而不干擾任何其他 BITS -- 因此類別定義中包含 setFlag($flag, $value) 函式,該函式只會設定選定的位元。以下是抽象基底類別定義

<?php

# BitwiseFlag.php

abstract class BitwiseFlag
{
protected
$flags;

/*
* 注意:這些函式被設為 protected 是為了防止外部程式碼
* 錯誤地設定 BITS。請參考擴展類別 'User' 如何
* 處理這個問題。
*
*/
protected function isFlagSet($flag)
{
return ((
$this->flags & $flag) == $flag);
}

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

?>

上面的類別是抽象類別,無法直接實例化,因此需要擴展。以下是一個簡單的擴展類別,名為 User,為了簡潔起見,內容已大幅精簡。請注意,我定義了 const 變數和使用這些變數的方法。

<?php

# User.php

require('BitwiseFlag.php');

class
User extends BitwiseFlag
{
const
FLAG_REGISTERED = 1; // $flags 的第 1 個位元,值為 1
const FLAG_ACTIVE = 2; // $flags 的第 2 個位元,值為 2
const FLAG_MEMBER = 4; // $flags 的第 3 個位元,值為 4
const FLAG_ADMIN = 8; // $flags 的第 4 個位元,值為 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' : '') .
']';
}
}

?>

這看起來像很多工作,但我們解決了許多問題,例如,使用和維護程式碼都很容易,而且取得和設定標誌值也很有意義。有了 User 類別,您現在可以了解位元標誌操作變得多麼容易和直觀。

<?php

require('User.php')

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

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

?>
40
grayda dot NOSPAM at DONTSPAM dot solidinc dot org
15 年前
最初,我發現位元遮罩是一個令人困惑的概念,並且找不到它的用途。因此,我整理了這段程式碼片段,以防其他人感到困惑

<?php

// 車輛可以擁有的各種詳細資訊
$hasFourWheels = 1;
$hasTwoWheels = 2;
$hasDoors = 4;
$hasRedColour = 8;

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

$isBike = $hasFourWheels & $bike; # False,因為 $bike 沒有四個輪子
$isGolfBuggy = $hasFourWheels & $golfBuggy; # True,因為 $golfBuggy 有四個輪子
$isFord = $hasFourWheels & $ford; # True,因為 $ford 有 $hasFourWheels

?>

您可以將其應用於許多事情,例如安全性

<?php

// 安全性權限:
$writePost = 1;
$readPost = 2;
$deletePost = 4;
$addUser = 8;
$deleteUser = 16;

// 使用者群組:
$administrator = $writePost | $readPosts | $deletePosts | $addUser | $deleteUser;
$moderator = $readPost | $deletePost | $deleteUser;
$writer = $writePost | $readPost;
$guest = $readPost;

// 檢查權限的函式
function checkPermission($user, $permission) {
if(
$user & $permission) {
return
true;
} else {
return
false;
}
}

// 現在我們應用所有這些!
if(checkPermission($administrator, $deleteUser)) {
deleteUser("Some User"); # 這會被執行,因為 $administrator 可以 $deleteUser
}

?>

一旦您了解它,它就非常有用!請記住將每個值提高 2 的冪,以避免問題
17
S?b.
19 年前
位元運算子的實際應用案例

<?php
// 我們想要知道這個顏色的紅、綠、藍值:
$color = 0xFEA946 ;

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

printf('紅色:%X (%d),綠色:%X (%d),藍色:%X (%d)',
$red, $red, $green, $green, $blue, $blue) ;

// 將顯示...
// 紅色 : FE (254), 綠色 : A9 (169), 藍色 : 46 (70)
?>
14
frankemeks77 at yahoo dot com
12 年前
剛開始學習位元平移運算子。

解決位元平移運算子最簡單的方法是,對於左移運算,每個步驟乘以 2,對於右移運算,每個步驟除以 2。

範例

左移運算
<?php echo 8 << 3; //64 ?>

//等同於
<?php echo 8 * 2 * 2 * 2; ?>

右移運算
<?php echo 8 >> 3; //1 ?>

//等同於
<?php echo ((8/2)/2)/2; //1 ?>

//在紙上計算:8/2 = 4/2 = 2/2 = 1
11
Silver
15 年前
關於 Bob 提到的旗標,我想指出一個 100% 安全的定義旗標的方法,那就是使用十六進位表示整數。

<?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
?>

當我有大量不同的旗標時,我總是避免使用十進位表示法,因為很容易拼錯像 2^20 (1048576) 這樣的數字。
10
zlel grxnslxves13 at hotmail dot com~=s/x/ee/g
19 年前
我參考了 Eric Swanson 關於 Perl 和 PHP 的 XOR 實作的文章。

實際上,這並不是 XOR 實作的問題,而是與 PHP 採用的寬鬆型別政策有很大關係。

在大多數情況下,自由切換 int 和 float 是好的,但是當您的值接近機器的字長時,就會發生問題。也就是說,32 位元的機器在處理接近 0x80000000 的值時會遇到問題,主要是因為 PHP 不支援無號整數。

使用 bindec/decbin 可以解決這個問題,作為執行無號整數 XOR 的變通方法,但這是真實的情況(我不是聲稱此程式碼的效能會更好,但這會是更好的教學程式碼)

<?php

function unsigned_xor32 ($a, $b)
{
$a1 = $a & 0x7FFF0000;
$a2 = $a & 0x0000FFFF;
$a3 = $a & 0x80000000;
$b1 = $b & 0x7FFF0000;
$b2 = $b & 0x0000FFFF;
$b3 = $b & 0x80000000;

$c = ($a3 != $b3) ? 0x80000000 : 0;

return ((
$a1 ^ $b1) |($a2 ^ $b2)) + $c;
}

$x = 3851235679;
$y = 43814;
echo
"<br>這是我們想要的值";
echo
"<br>3851262585";

echo
"<br>整數值的原生 xor 運算的結果會被視為有號整數";
echo
"<br>".($x ^ $y);

echo
"<br>因此我們分別執行 MSB";
echo
"<br>".unsigned_xor32($x, $y);

?>

這確實是基礎知識,但對於那些在大學錯過這些知識的人來說,這裡似乎有一些關於二補數的內容

http://www.evergreen.edu/biophysics/technotes/program/2s_comp.htm
6
m0sh at hotmail dot com
16 年前
@greenone - 不錯的函式,謝謝。我已將它調整為金鑰使用

<?php
function bitxor($str, $key) {
$xorWidth = PHP_INT_SIZE*8;
// 分割
$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;
}
?>
9
zewt at hotmail dot com
17 年前
如果你使用位元運算,你必須確保你的變數是整數,否則你可能會得到不正確的結果。

我建議永遠使用

(int)$var & (int)$var2

當你對完全不合邏輯的結果進行疑難排解時,這將為你省去許多麻煩。
8
zooly at globmi dot com
15 年前
這是位元左旋轉和右旋轉的範例。

請注意,此函式僅適用於十進位數字 - 其他類型可以使用 pack() 轉換。

<?php

function rotate ( $decimal, $bits) {

$binary = decbin($decimal);

return (
bindec(substr($binary, $bits).substr($binary, 0, $bits))
);

}

// 將 124 (1111100) 向左旋轉 1 個位元

echo rotate(124, 1);

// = 121 (1111001)

// 將 124 (1111100) 向右旋轉 3 個位元

echo rotate(124, -3);

// = 79 (1001111)

?>
9
vivekanand dot pathak25 at gmail dot com
11 年前
$a = 9;
$b = 10;
echo $a & $b;

位值 128 64 32 16 8 4 2 1
$a 0 0 0 0 1 0 0 1 =9
$b 0 0 0 0 1 0 1 0 =10

結果 8

它們唯一共有的位元是 8 的位元。所以回傳 8。

$a = 36;
$b = 103;
echo $a & $b;

位值 128 64 32 16 8 4 2 1
$a 0 0 1 0 0 1 0 0 =36
$b 0 1 1 0 0 1 1 1 =103

結果 32+4 = 36
這兩個數唯一共有的位元是 32 和 4 的位元,加總後回傳 36。

$a = 9;
$b = 10;
echo $a | $b;

位值 128 64 32 16 8 4 2 1
$a 0 0 0 0 1 0 0 1 =9
$b 0 0 0 0 1 0 1 0 =10

結果 8+2+1 = 11
3 個位元被設定,在 8、2 和 1 的欄位。將它們加總 8+2+1,得到 11

$a = 9;
$b = 10;
echo $a ^ $b;

位值 128 64 32 16 8 4 2 1
$a 0 0 0 0 1 0 0 1 =9
$b 0 0 0 0 1 0 1 0 =10

結果 2+1 = 3
它們各自有設定,但不共有的 2 位元和 1 位元。所以 2+1 = 3
3
ASchmidt at Anamera dot net
5 年前
在位元遮罩中設定、取消設定和測試單個和多個位元

<?php
const FLAG_A = 0b0001,
FLAG_B = 0b0010,
FLAG_C = 0b0100,
FLAG_D = 0b1000;

const
COMBO_BC = FLAG_B | FLAG_C;

$bitmask = 0b000;

// 設定個別旗標。
$bitmask |= FLAG_B; // 設定 FLAG_B (=2)
$bitmask |= FLAG_C; // 也設定 FLAG_C (=4)

// 測試單個或多個旗標。
echo (bool)( $bitmask & FLAG_B ); // True,B 已設定。

echo (bool)( $bitmask & (FLAG_A | FLAG_B) ); // True,A 或 B 已設定。

echo (bool)( $bitmask & FLAG_B and $bitmask & FLAG_C ); // True,B 和 C 都已設定。
echo (bool)( ( $bitmask & (FLAG_B | FLAG_C) ) ^ (FLAG_B | FLAG_C) ); // 如果 B 和 C 都已設定,則為 False。
echo (bool)( ( $bitmask & COMBO_BC ) ^ COMBO_BC ); // 如果 B 和 C 都已設定,則為 False。

echo (bool)( $bitmask & FLAG_C and $bitmask & FLAG_D ); // False,C 和 D 不是都已設定。
echo (bool)( ( $bitmask & (FLAG_C | FLAG_D) ) ^ (FLAG_C | FLAG_D) ); // 如果 C 和 D 不是都已設定,則為 True。

// 重設單個旗標。
$bitmask &= $bitmask ^ FLAG_B; // 取消設定 B
$bitmask &= $bitmask ^ FLAG_A; // A 仍然未設定。
var_dump( $bitmask ); // 只有 C 仍然設定 (=4)

// 重設多個旗標。
$bitmask &= $bitmask ^ ( FLAG_C | FLAG_D ); // 取消設定 C 和/或 D
var_dump( $bitmask ); // 沒有旗標設定 (=0)
4
josh at joshstrike dot com
13 年前
更多是為了我自己參考... 如果你需要迭代每個可能的二進制組合,其中 $n 個旗標在 $bits 長度的遮罩中設定為 1

<?php
echo masksOf(3,10);

function
masksOf($n,$bits) {
$u = pow(2,$bits)-1; //起始值,所有旗標開啟。
$masks = array();
while (
$u>0) {
$z = numflags($u);
if (
$z==$n) array_push($masks,$u);
$u--;
}
return (
$masks);
}

function
numflags($n) {
$k = 0;
while (
$n) {
$k += $n & 1;
$n = $n >> 1;
}
return (
$k);

// 另一個方法:
// $u = 0;
// for ($k=1;$k<=$n;$k*=2) {
// $u+=($n&$k?1:0);
// }
// return ($u);
}
?>
4
amckenzie4 at gmail dot com
14 年前
如果像我一樣,你從未想過 PHP 如何處理二進制,那麼位元 NOT 的輸出可能會讓你感到困惑。例如,這個

$bin = 2;
$notbin = ~$bin;

echo "Bin: " . decbin($bin) . " !bin: " . decbin($notbin) . "\n";

會回傳這個

Bin: 10 !bin: 1111111111111111111111111111111111111111111111111111111111111101

原因是所有的二進制數都被視為 32 位元,即使你手動輸入的位數較少。為了得到我預期的結果 (01),必須將結果與我想要的位數進行 AND 運算:在這個例子中是 2(十進制數 3)。請注意,所有回傳值都會從左邊移除 0,直到它們達到設定為 1 的位元。繼續上面的例子,以下程式碼

$notbin_2 = ~$bin & '3';
echo "!bin & 3: " . decbin($notbin_2) . "\n";

會回傳這個

!bin & 3: 1

請注意,實際值是 31 個 0 後面跟著 1,但沒有顯示 0。這可能是一件好事。

此外,NOT 運算子使用二補數,這表示你得到的數字可能比你預期的更奇怪:使用二補數表示 ~2 == -3。網路上有很多關於二補數的良好解釋,所以這裡就不深入探討這個問題了。

如果你只是想反轉一串位元而沒有任何解讀,你可以使用像這樣的函式

function bitnot($bin)
{
$not = "";
for ($i = 0; $i < strlen($bin); $i++)
{
if($bin[$i] == 0) { $not .= '1'; }
if($bin[$i] == 1) { $not .= '0'; }
}
return $not;
}

它接受任何長度的二進制字串,反轉位元,並回傳新的字串。然後你可以將它視為二進制數,使用 bindec() 將它轉換為十進制,或任何你想要的用途。

我希望這對某人有所幫助,就像它在一週前對我有所幫助一樣!
5
Tbrendstrup
19 年前
請注意,位移運算子是算術運算,而不是像 C 語言那樣的邏輯運算。你可能會在負數上得到意想不到的結果,請參閱 http://en.wikipedia.org/wiki/Bitwise_operation

這是一個進行邏輯右移的函式。

<?php

function lshiftright($var,$amt)
{
$mask = 0x40000000;
if(
$var < 0)
{
$var &= 0x7FFFFFFF;
$mask = $mask >> ($amt-1);
return (
$var >> $amt) | $mask;
}
return
$var >> $amt;
}

$val = -10;

printf("負整數的算術位移<br>%1\$032b<br>%2\$032b<br>%1\$0d<br>%2\$0d<br>",$val, $val >> 1 );

printf("負整數的邏輯位移<br>%1\$032b<br>%2\$032b<br>%1\$0d<br>%2\$0d<br>",$val, lshiftright($val, 1));

printf("正整數的邏輯位移<br>%1\$032b<br>%2\$032b<br>%1\$0d<br>%2\$0d<br>",-$val, lshiftright(-$val, 1));
?>

輸出結果為

負整數的算術位移
11111111111111111111111111110110
11111111111111111111111111111011
-10
-5

負整數的邏輯位移
11111111111111111111111111110110
01111111111111111111111111111011
-10
2147483643

正整數的邏輯位移
00000000000000000000000000001010
00000000000000000000000000000101
10
5
6
spencer-p-moy at example dot com
13 年前
NOT 或補數運算子 ( ~ ) 和負二進位數字可能會令人困惑。

~2 = -3,因為您使用公式 ~x = -x - 1。十進位數字的位元補數是該數字的負數再減 1。

注意:以下範例僅使用 4 個位元,但實際上 PHP 使用 32 個位元。

將負十進位數字(例如:-3)轉換為二進位需要 3 個步驟
1) 將十進位數字的正數版本轉換為二進位(例如:3 = 0011)
2) 反轉位元(例如:0011 變成 1100)
3) 加 1(例如:1100 + 0001 = 1101)

您可能想知道 1101 如何等於 -3。PHP 使用「2 的補數」方法來呈現負二進位數字。如果最左邊的位元是 1,則二進位數字為負數,您將反轉位元並加 1。如果它是 0,則為正數,您不必執行任何操作。因此,0010 將為正 2。如果它是 1101,則為負數,您反轉位元以獲得 0010。加 1,您得到 0011,等於 -3。
5
aba at example dot com
13 年前
確實,如果左側和右側的參數都是字串,則按位元運算子將對字元的 ASCII 值進行運算。但是,要完成這個句子,必須加上補充說明。
值得指出的是,十進位字元的 ASCII 值具有不同的二進位值。

<?php
if (('18' & '32') == '10') {
echo
ord('18'); //回傳十進位值 49,其二進位值為 110001
echo ord('32'); //回傳十進位值 51,其二進位值為 110011
echo ord('10'); //回傳十進位值 49,其二進位值為 110001
//因此 110001 & 110011 = 110001
}
?>
3
icy at digitalitcc dot com
19 年前
假設...您真的希望在您的位元遮罩中擁有超過 31 個位元。而且您不想使用浮點數。那麼,一種解決方案是使用一個位元遮罩陣列,該陣列透過某種介面來存取。

這是我的解決方案:一個類別用於儲存作為位元遮罩的整數陣列。它可以容納最多 66571993087 個位元,並且在沒有位元儲存在其中時會釋放未使用的位元遮罩。

<?php
/*
無限* 位元和位元處理的一般概念。

*抱歉,不是無限的。

顯而易見地,bitmask 類別在儲存位元方面的唯一限制是
索引數字的最大限制,在 32 位元整數系統上是 2^31 - 1,
因此 2^31 * 31 - 1 = 66571993087 位元,假設浮點數是 64 位元或類似的東西。
我確信這對於任何事情來說都足夠了...我希望 :D。
*/

DEFINE('INTEGER_LENGTH',31); // 愚蠢的符號位元。

class bitmask
{
protected
$bitmask = array();

public function
set( $bit ) // 設定某些位元
{
$key = (int) ($bit / INTEGER_LENGTH);
$bit = (int) fmod($bit,INTEGER_LENGTH);
$this->bitmask[$key] |= 1 << $bit;
}

public function
remove( $bit ) // 移除某些位元
{
$key = (int) ($bit / INTEGER_LENGTH);
$bit = (int) fmod($bit,INTEGER_LENGTH);
$this->bitmask[$key] &= ~ (1 << $bit);
if(!
$this->bitmask[$key])
unset(
$this->bitmask[$key]);
}

public function
toggle( $bit ) // 切換某些位元
{
$key = (int) ($bit / INTEGER_LENGTH);
$bit = (int) fmod($bit,INTEGER_LENGTH);
$this->bitmask[$key] ^= 1 << $bit;
if(!
$this->bitmask[$key])
unset(
$this->bitmask[$key]);
}

public function
read( $bit ) // 讀取某些位元
{
$key = (int) ($bit / INTEGER_LENGTH);
$bit = (int) fmod($bit,INTEGER_LENGTH);
return
$this->bitmask[$key] & (1 << $bit);
}

public function
stringin($string) // 讀取一個位元字串,其長度可達最大位元數。
{
$this->bitmask = array();
$array = str_split( strrev($string), INTEGER_LENGTH );
foreach(
$array as $key => $value )
{
if(
$value = bindec(strrev($value)))
$this->bitmask[$key] = $value;
}
}

public function
stringout() // 列印出您的漂亮的小位元字串
{
$string = "";

$keys = array_keys($this->bitmask);
sort($keys, SORT_NUMERIC);

for(
$i = array_pop($keys);$i >= 0;$i--)
{
if(
$this->bitmask[$i])
$string .= sprintf("%0" . INTEGER_LENGTH . "b",$this->bitmask[$i]);
}
return
$string;
}

public function
clear() // 清除!
{
$this->bitmask = array();
}

public function
debug() // 查看您的 bitmask 陣列中發生了什麼
{
var_dump($this->bitmask);
}
}
?>

它將正整數輸入視為位元,因此您不必自己處理 2 的冪。

<?php
$bitmask
= new bitmask();

$bitmask->set(8979879); // 無所謂

$bitmask->set(888);

if(
$bitmask->read(888))
print
'Happy!\n';

$bitmask->toggle(39393); // 隨便啦

$bitmask->remove(888);

$bitmask->debug();

$bitmask->stringin("100101000101001000101010010101010
00000001000001"
);

print
$bitmask->stringout() . "\n";

$bitmask->debug();

$bitmask->clear();

$bitmask->debug();
?>

會產生

Happy!

array(2) {
[289673]=>
int(65536)
[1270]=>
int(8388608)
}

0000000000000001001010001010010001010100101010100
0000001000001

array(2) {
[0]=>
int(355106881)
[1]=>
int(37970)
}

array(0) {
}
3
ivoras at gmail dot com
13 年前
作為額外的好奇心,由於某種原因,運算 ("18" & "32") 的結果是 "10"。換句話說,盡量避免在字串上使用二元運算子 :)
2
Eric Swanson
19 年前
Perl vs. PHP 的 ^ 運算子實作

在嘗試將 Perl 模組翻譯成 PHP 後,我意識到 Perl 的 ^ 運算子實作與 PHP 的實作不同。預設情況下,Perl 將變數視為浮點數,而 PHP 將其視為整數。我能夠通過在 Perl 模組中聲明「use integer;」來驗證 PHP 對運算子的使用,該模組輸出了與 PHP 使用的完全相同的結果。

邏輯上的決定是在 PHP 中使用 ^ 運算子時將每個變數轉換為 (float)。但是,這不會產生相同的結果。在將我的頭撞牆約半小時後,我發現了一個寶石,並使用 PHP 中的二進位-十進位轉換編寫了一個函數。

/*
由於我沒有太多位元運算的經驗,我無法告訴您這是最佳解決方案,但它絕對是一個最終有效且始終返回與 Perl 提供完全相同結果的解決方案。
*/
function binxor($a, $b) {
return bindec(decbin((float)$a ^ (float)$b));
}

// 常規 PHP 程式碼不會產生與 Perl 相同的結果
$result = 3851235679 ^ 43814; //= -443704711

// 為了獲得與 Perl 相同的結果
$result = binxor(3851235679, 43814); //= 3851262585
//耶!

//若要查看差異,請嘗試以下程式碼
$a = 3851235679 XOR 43814;
$b = 3851235679 ^ 43814; //整數結果
$c = (float)3851235679 ^ (float)43814; //與 $b 相同
$d = binxor(3851235679, 43814); //與 Perl 相同!!

echo("A: $a<br />");
echo("B: $b<br />");
echo("C: $c<br />");
echo("D: $d<br />");
1
forlamp at msn dot com
16 年前
用於 32 位元的二補數邏輯運算。

將 $x 傳遞給此函式時,必須是 (int) 型態才能正常運作。

function comp2($x) // 32位元位元反相
{
$mask = 0x80000000;

if ($x < 0)
{
$x &= 0x7FFFFFFF;
$x = ~$x;

return $x ^ $mask;
}
else
{
$x = $x ^ 0x7FFFFFFF;

return $x | $mask;
}
}
2
cw3theophilus at gmail dot com
15 年前
對於那些正在尋找 PHP 中循環位移函式(對於密碼學函式特別有用)且能處理負值的人來說,這是我寫的一個小函式

(注意:我花了一整天的時間才讓它能處理負數的 $num 值(我搞不懂為什麼有時可以運作,有時卻不行),因為 PHP 只有算術右移,而沒有像我習慣的邏輯右移。例如,0x80000001>>16 會輸出(以二進位表示)"1111 1111 1111 1111 1000 0000 0000 0000",而不是你預期的 "0000 0000 0000 0000 1000 0000 0000 0000"。為了修正這個問題,你必須套用遮罩(使用位元 & 運算子),其值等於 0x7FFFFFFF 向右移位比你實際位移量少一。)

<?php
function circular_shift($num,$offset) { //執行非破壞性循環位元移位,若 offset 為正值則向左移位,若為負值則向右移位
$num=(int)$num;
$mask=0x7fffffff; //遮罩是為了處理 PHP 只做算術右移而非邏輯右移的事實,例如,PHP 在右移負值時不會給出預期的輸出
if ($offset>0) {
$num=($num<<$offset%32) | (($num>>(32-$offset%32)) & ($mask>>(31-$offset%32)));
}
elseif (
$offset<0){
$offset=abs($offset);
$num=(($num>>$offset%32) & ($mask>>(-1+$offset%32))) | ($num<<(32-$offset%32));
}
return
$num;
}
?>
1
sag at ich dot net
11 年前
我重新實作了位元 NOT (~) 的功能

protected function flipBin($number) {
$bin = str_pad(base_convert($number, 10, 2), 32, 0, STR_PAD_LEFT);
for ($i = 0; $i < 32; $i++) {
switch ($bin{$i}) {
case '0'
$bin{$i} = '1';
break;
case '1'
$bin{$i} = '0';
break;
}
}
return bindec($bin);
}

好處是,它能處理大於 MAX_INT 的數字
1
Adam
14 年前
請注意運算子的優先順序。

例如,您可能想檢查第二個位元是否已設定

<?php
if ($x & 2 == 2) {
/* 程式碼 */
}
?>

與以下程式碼不同

<?php
if (($x & 2) == 2) {
/* 程式碼 */
}
?>

應該使用後者。
0
Anonymous
13 年前
為了清楚說明為什麼 ("18" & "32") 的結果是 "10"。
1) 它們都是字串,
2) "&" 運算子在字串上的運作方式是,從每個字串中取出每個「字元」,並在它們之間執行位元 & 運算,然後將此值加入結果字串

所以
"18" 由兩個字元組成:0x31, 0x38
"32" 由兩個字元組成:0x33, 0x32
----結果-----
0x31 & 0x33 = 0x31 => "1"
0x38 & 0x32 = 0x30 => "0"

結果是 "10",這是 100% 正確的。
-1
erich at seachawaii dot com
11 年前
關於負數位移值的注意事項,正如文件所述,每次位移都是將整數乘以或除以 2(分別是向左或向右移)。這表示負數的位移值(右運算元)會影響位移的符號,而不是像我預期的那樣影響位移的方向。
例如,0xff >> -2 的結果是 0x0
而 0xff << -2 的結果是 0xFFFFFFFFC0000000(取決於 PHP_INT_MAX)
-1
Core Xii
14 年前
在對字串做 XOR 運算時要非常小心!如果其中一個值是空的(0、''、null),結果也會是空的!

<?php
var_dump
(1234 ^ 0); // int(1234)
var_dump(1234 ^ ''); // int(1234)
var_dump(1234 ^ null); // int(1234)
var_dump('hello world' ^ 0); // int(0)
var_dump('hello world' ^ ''); // string(0) ""
var_dump('hello world' ^ null); // int(0)
?>

這看起來相當不一致。整數與零做 XOR 運算會得出原始整數。但是字串與空值做 XOR 運算會得出空值!

我的密碼雜湊函式總是傳回相同的雜湊值...因為我總是將它與有時為空的鹽值做 XOR 運算!
-2
Bob
15 年前
這是一個簡單的方法,可以使用位元運算來實現「旗標」功能。
我的意思是管理一組可以開啟或關閉的選項,其中可以設定零個或多個選項,並且每個選項只能設定一次。(如果您熟悉 MySQL,請把它想像成 'set' 資料類型)。
注意:對資深的程式設計師來說,這很明顯。

以下是程式碼
<?php
function set_bitflag(/*變數長度引數*/)
{
$val = 0;
foreach(
func_get_args() as $flag) $val = $val | $flag;
return
$val;
}
function
is_bitflag_set($val, $flag)
{
return ((
$val & $flag) === $flag);
}
// 定義您的旗標
define('MYFLAGONE', 1); // 0001
define('MYFLAGTWO', 2); // 0010
define('MYFLAGTHREE', 4); // 0100
define('MYFLAGFOUR', 8); // 1000
?>

我應該指出:您的旗標儲存在單一整數中。您可以在單一整數中儲存許多旗標。

若要使用我的函式,假設您想要設定 MYFLAGONE 和 MYFLAGTHREE,您可以使用
<?php
$myflags
= set_bitflags(MYFLAGONE, MYFLAGTHREE);
?>
注意:您可以傳遞任意數量的旗標給 set_bitflags()。

當您稍後想要測試是否已設定特定旗標時,請使用例如:
<?php
if(is_bitflag_set($myflags, MYFLAGTWO))
{
echo
"MYFLAGTWO 已設定!";
}
?>

唯一比較棘手的部分是定義您的旗標。以下是流程
1. 寫下您的旗標列表
2. 計算它們的數量
3. 將列表中的最後一個旗標定義為 1 乘以 2 的 <數量> 次方減一。(也就是說,1*2^(<數量>-1) )
3. 從最後一個到第一個,反向瀏覽列表,將每個旗標定義為前一個旗標的一半。當您到達第一個時,應該會達到 1

如果您想更了解二進位數字、位元和位元運算,維基百科頁面有很好的解釋 - http://en.wikipedia.org/wiki/Bitwise_operation.
To Top