PHP Conference Japan 2024

位运算符

位运算符允许评估和操作整数中特定位。

位运算符
示例 名称 结果
$a & $b $a$b 中都设置的位被设置。
$a | $b 或(包含或) $a$b 中设置的位被设置。
$a ^ $b 异或(排他或) $a$b 中设置但并非两者都设置的位被设置。
~ $a $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 对整数执行按位与、或和异或运算

<?php
/*
* 忽略顶部的部分,
* 它只是格式化以使输出更清晰。
*/

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

echo <<<EOH
--------- --------- -- ---------
结果值运算符测试
--------- --------- -- ---------
EOH;


/*
* 以下是示例。
*/

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

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

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

echo
"\n 按位异或 (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 对字符串执行按位异或运算

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

警告

对于超过 PHP_INT_MAX 的数字的位操作,请使用 gmp 扩展中的函数。

添加注释

用户贡献的注释 27 条注释

wbcarts at juno dot com
12 年前
自定义 PHP 对象的位标志

有时我需要一个自定义 PHP 对象来保存多个布尔 TRUE 或 FALSE 值。我可以轻松地为每个值包含一个变量,但一如既往,代码有很快变得笨拙的方式。即使一开始看起来有点过头,但更智能的方法似乎总是答案。



我从一个抽象基类开始,该基类将包含一个名为 $flags 的整型变量。这个简单的整数可以存储 32 个 TRUE 或 FALSE 布尔值。另一个需要考虑的是,只需设置某些位的值,而不会干扰任何其他位——因此,类定义中包含了 setFlag($flag, $value) 函数,该函数只会设置选定的位。以下是抽象基类的定义

<?php

# BitwiseFlag.php

抽象类 BitwiseFlag
{
protected
$flags;

/*
* 注意:这些函数被保护起来,以防止外部代码
* 错误地设置位。请查看扩展类 '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 的简单扩展——为了清晰起见,它被严重截断。请注意,我正在定义常量变量和方法来使用它们。

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

?>
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 有四个轮子

?>

并且您可以将此应用于许多事物,例如安全性



<?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 的幂,以避免出现问题。
S?b.
19 年前
位运算符的实际案例

<?php
// 我们想知道此颜色的红色、绿色和蓝色值:
$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) ;

// 将显示...
// Red : FE (254), Green : A9 (169), Blue : 46 (70)
?>
frankemeks77 at yahoo dot com
12 年前
正在学习位移运算符。

解决位移运算符最简单的方法是分别将每一步乘以或除以 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
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
// ... 直到 2^31
?>

当我有大量不同的标志时,我总是避免使用十进制表示法,因为很容易拼错像 2^20(1048576)这样的数字。
zlel grxnslxves13 at hotmail dot com~=s/x/ee/g
19 年前
我参考了 Eric Swanson 关于 Perl 与 PHP 的异或实现的帖子。

实际上,这不是异或实现的问题,而更多地与 PHP 采用的松散类型策略有关。

在 int 和 float 之间自由切换在大多数情况下都很好,但当你的值接近机器的字长时就会出现问题。也就是说,32 位机器在接近 0x80000000 的值时会遇到问题 - 主要是由于 PHP 不支持无符号整数。

使用 bindec/decbin 可以作为解决此问题的变通方案来执行无符号 int 异或,但这是真实情况(我并不是说此代码性能会更好,但它将是更好的教学代码)

<?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>对整数执行原生异或运算的结果被视为有符号整数";
echo
"<br>".($x ^ $y);

echo
"<br>因此,我们分别执行 MSB";
echo
"<br>".unsigned_xor32($x, $y);

?>

这确实是基础知识,但对于那些在大学里错过这些知识的人来说,这里似乎有一些关于二进制补码的内容。

http://www.evergreen.edu/biophysics/technotes/program/2s_comp.htm
[email protected]
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;
}
?>
[email protected]
17年前
如果你使用按位运算,你**必须**确保你的变量是整数,否则你可能会得到错误的结果。

我建议**始终**

(int)$var & (int)$var2

这将帮助你在排查完全不合逻辑的结果时避免很多麻烦。
[email protected]
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)

?>
[email protected]
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。
[email protected]
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) ); // False 如果 B 和 C 都已设置。
echo (bool)( ( $bitmask & COMBO_BC ) ^ COMBO_BC ); // False 如果 B 和 C 都已设置。

echo (bool)( $bitmask & FLAG_C and $bitmask & FLAG_D ); // False,C 和 D 并非都已设置。
echo (bool)( ( $bitmask & (FLAG_C | FLAG_D) ) ^ (FLAG_C | FLAG_D) ); // True,如果 C 和 D 并非都已设置。

// 重置单个标志。
$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)
[email protected]
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);
}
?>
amckenzie4 at gmail dot com
14年前
如果,像我一样,你从未想过PHP是如何处理二进制的,那么按位取反的结果可能会让你感到困惑。例如,这段代码

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

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

会返回以下结果

Bin: 10 !bin: 1111111111111111111111111111111111111111111111111111111111111101

原因是所有二进制数都被视为32位,即使你手动输入的位数较少。为了得到我期望的结果(01),需要将结果与我想要的位数进行按位与运算:在本例中,是2(十进制数3)。请注意,所有返回值都会从左侧移除零,直到遇到一个设置为1的位。继续上面的例子,以下代码

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

会返回以下结果

!bin & 3: 1

请注意,实际值是一个由31个零和一个1组成的字符串,但零没有显示出来。这可能是一件好事。

此外,取反运算符使用补码,这意味着你得到的结果可能比你预期的更加奇怪:使用补码意味着 ~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()将其转换为十进制数,或者任何你想要的操作。

希望这对某些人有所帮助,就像一周前对我有帮助一样!
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
spencer-p-moy at example dot com
13年前
取反或补码运算符( ~ )和负二进制数可能令人困惑。

~2 = -3,因为你使用了公式 ~x = -x - 1。十进制数的按位补码是该数的负数减1。

注意:以下示例仅使用4位,但实际上PHP使用32位。

将负十进制数(例如 -3)转换为二进制数需要三个步骤
1) 将十进制数的正数版本转换为二进制数(例如:3 = 0011)
2) 翻转位(例如:0011 变为 1100)
3) 加1(例如:1100 + 0001 = 1101)

你可能想知道 1101 如何等于 -3。PHP 使用“补码”方法来表示负二进制数。如果最左边的位是1,则二进制数为负数,你需要翻转位并加1。如果是0,则为正数,无需进行任何操作。因此,0010 表示正数2。如果是1101,则为负数,你需要翻转位得到0010。加1,得到0011,它等于-3。
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
}
?>
icy at digitalitcc dot com
19 年前
假设... 你真的想要在你的快乐位掩码中拥有超过31位的可用位。并且你不想使用浮点数。因此,一种解决方案是使用一个位掩码数组,通过某种接口进行访问。

这是我的解决方案:一个类,用于存储一个整数数组作为位掩码。它最多可以容纳66571993087位,并在没有位存储在其中时释放未使用的位掩码。



<?php
/*
无限* 位以及一般的位操作。

*对不起,不是无限的。

可以想象,位掩码类在存储位方面的唯一限制将是
索引号的最大限制,在 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() // 查看位掩码数组中发生了什么
{
var_dump($this->bitmask);
}
}
?>

它将正整数输入视为一个位,因此您无需自己处理 2 的幂。

<?php
$bitmask
= new bitmask();

$bitmask->set(8979879); // 无论如何

$bitmask->set(888);

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

$bitmask->toggle(39393); // 等等

$bitmask->remove(888);

$bitmask->debug();

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

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

$bitmask->debug();

$bitmask->clear();

$bitmask->debug();
?>

将产生

开心!

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

0000000000000001001010001010010001010100101010100
0000001000001

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

数组(0) {
}
ivoras at gmail dot com
13年前
作为额外的好奇心,出于某种原因,操作 ("18" & "32") 的结果为 "10"。换句话说,尝试避免在字符串上使用二进制运算符 :)
Eric Swanson
19 年前
Perl 与 PHP 中 ^ 运算符的实现

在尝试将 Perl 模块转换为 PHP 后,我意识到 Perl 中 ^ 运算符的实现与 PHP 中的实现不同。默认情况下,Perl 将变量视为浮点数,而 PHP 将变量视为整数。我能够通过在 Perl 模块中声明 "use integer;" 来验证 PHP 对运算符的使用,这输出了与 PHP 使用完全相同的结果。

逻辑上的决定是在 PHP 中使用 ^ 运算符时将每个变量转换为 (float)。但是,这不会产生相同的结果。在花了大约半小时撞墙后,我发现了一个宝石,并编写了一个使用 PHP 中二进制-十进制转换的函数。

/*
由于我对位运算的经验不多,所以我不能告诉你这是最好的解决方案,但它肯定是一个最终有效的解决方案,并且始终返回 Perl 提供的完全相同的结果。
*/
函数 binxor($a, $b) {
返回 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 />");
forlamp at msn dot com
16年前
32 位的二进制补码逻辑运算。

$x 在传递给此函数时必须为 (int) 才能正常工作。

函数 comp2($x) // 32 位位补码
{
$mask = 0x80000000;

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

return $x ^ $mask;
}
否则
{
$x = $x ^ 0x7FFFFFFF;

return $x | $mask;
}
}
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 右移比您要移动的偏移量少 1。)

<?php
function circular_shift($num,$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;
}
?>
sag at ich dot net
11年前
我重新实现了按位非 (~)

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 的数字。
Adam
14年前
注意运算顺序。

例如,您可能希望检查第二位是否已设置

<?php
if ($x & 2 == 2) {
/* 代码 */
}
?>

与以下情况不同

<?php
if (($x & 2) == 2) {
/* 代码 */
}
?>

并且应该使用后者。
Anonymous
13年前
为了更清楚地说明为什么 ("18" & "32") 等于 "10"。
1) 它们都是字符串,
2) "&" 运算符对字符串的操作是,从每个字符串中获取每个 !字符!,并在它们之间执行按位 & 运算,并将结果值添加到结果字符串中。

所以
"18" 由两个字符组成:0x31、0x38
"32" 由两个字符组成:0x33、0x32
----结果-----
0x31 & 0x33 = 0x31 => "1"
0x38 & 0x32 = 0x30 => "0"

结果是 "10",这是 100% 正确的。
erich at seachawaii dot com
11年前
关于负移位值需要注意的是,正如文档中所述,每次移位都是整数乘以或除以 2(分别向左或向右)。这意味着负移位值(右侧操作数)影响移位的符号,而不是移位的方向,这与我的预期不符。
例如,0xff >> -2 的结果为 0x0
而 0xff << -2 的结果为 0xFFFFFFFFC0000000(取决于 PHP_INT_MAX)
Core Xii
14年前
对字符串执行异或运算时要非常小心!如果其中一个值为空(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)
?>

这种行为似乎很不一致。整数与零异或的结果是原始整数。但是字符串与空值异或的结果为空值!

我的密码哈希函数总是返回相同的哈希值……因为我用有时为空的盐对其进行了异或!
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 次方。(即 1*2^(<计数>-1))
3. 从后向前遍历列表,从最后一个到第一个,将每个标志定义为前一个标志的一半。当您到达第一个时,您应该到达 1。



如果您想更好地理解二进制数、位和位运算,维基百科页面对此有很好的解释 - http://en.wikipedia.org/wiki/Bitwise_operation.
To Top