Los operadores sobre bits permiten manipular los bits en un entero.
Los operadores sobre bits
Ejemplo
Nombre
Resultado
$a & $b
And (Y)
Los bits posicionados a 1 en $a Y en
$b son posicionados a 1.
$a | $b
Or (O)
Los bits posicionados a 1 en $a O $b
son posicionados a 1.
$a ^ $b
Xor (o exclusivo)
Los bits posicionados a 1 en $a O en
$b pero no en los dos son posicionados a 1.
~ $a
Not (No)
Los bits que están posicionados a 1 en $a
son posicionados a 0, y viceversa.
$a << $b
Desplazamiento a la izquierda
Desplaza los bits de $a, $b veces
a la izquierda (cada desplazamiento equivale a una multiplicación por 2).
$a >> $b
Desplazamiento a la derecha
Desplaza los bits de $a, $b veces
a la derecha (cada desplazamiento equivale a una división por 2).
El desplazamiento de bits en PHP es aritmético.
Los bits que son desplazados fuera del entero se pierden.
Los desplazamientos a la izquierda hacen aparecer ceros a la derecha,
mientras que el bit de signo es desplazado a la izquierda, lo que significa
que el signo del entero no es preservado.
Los desplazamientos a la derecha desplazan también el bit de signo a la
derecha, lo que significa que el signo es preservado.
Utilícense paréntesis para asegurarse de que la
precedencia
deseada sea aplicada correctamente. Por ejemplo,
$a & $b == true aplica primero
la igualdad, y luego el AND lógico, mientras que
($a & $b) == true aplica primero el
AND lógico, y luego la igualdad.
Si los dos operandos para los operadores &,
| y ^ son strings,
entonces la operación será realizada sobre los valores ASCII de los caracteres y el
resultado será un string. En todos los otros casos, los dos operandos serán
convertidos en entero
y el resultado será un entero.
Si el operando para el operador ~ es un string,
la operación será realizada sobre los caracteres ASCII que componen el string y el
resultado será un string. De lo contrario, el operando y el resultado serán tratados como enteros.
Los operandos y el resultado de los operadores << y
>> son tratados como enteros.
El informe de errores de PHP utiliza campos de bits,
que son una ilustración de la extinción de bits.
Para mostrar los errores, excepto las notificaciones, las
instrucciones del php.ini son :
E_ALL & ~E_NOTICE
Esto se comprende comparando con E_ALL :
00000000000000000111011111111111
Luego, apagando el valor de E_NOTICE...
00000000000000000000000000001000
... y invirtiéndolo a través de ~:
11111111111111111111111111110111
Finalmente, se utiliza el AND lógico (&) para leer los bits activados
en las dos valores :
00000000000000000111011111110111
Otro medio de llegar a este resultado es utilizar
el OU exclusivo (^), que busca
los bits que están activados solo en una de las
dos valores, exclusivamente :
E_ALL ^ E_NOTICE
error_reporting también puede ser utilizado para
ilustrar la activación de bits. Para mostrar
únicamente los errores y los errores recuperables,
se utiliza :
E_ERROR | E_RECOVERABLE_ERROR
Este enfoque combina E_ERROR
00000000000000000000000000000001
y E_RECOVERABLE_ERROR
00000000000000000001000000000000
Con el operador OR (|) para asegurarse de
que los bits están activados en una de las dos valores :
00000000000000000001000000000001
Ejemplo #1 Operaciones sobre bits y enteros
<?php /* * Ignórese esta parte, * es solo formato para clarificar los resultados */
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); }
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); }
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]
$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:
// 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
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:
I refer to Eric Swanson's post on Perl VS PHP's implementation of xor.
Actually, this is not an issue with the implementation of XOR, but a lot more to do with the lose-typing policy that PHP adopts.
Freely switching between int and float is good for most cases, but problems happen when your value is near the word size of your machine. Which is to say, 32-bit machines will encounter problems with values that hover around 0x80000000 - primarily because PHP does not support unsigned integers.
using bindec/decbin would address this issue as a work-around to do unsigned-int xor, but here's the real picture (i'm not claiming that this code will perform better, but this would be a better pedagogical code):
// Testing single or multiple flags. echo (bool)( $bitmask & FLAG_B ); // True, B is set.
echo (bool)( $bitmask & (FLAG_A | FLAG_B) ); // True, A or B is set.
echo (bool)( $bitmask & FLAG_B and $bitmask & FLAG_C ); // True, B and C are set. echo (bool)( ( $bitmask & (FLAG_B | FLAG_C) ) ^ (FLAG_B | FLAG_C) ); // False if B and C are set. echo (bool)( ( $bitmask & COMBO_BC ) ^ COMBO_BC ); // False if B and C are set.
echo (bool)( $bitmask & FLAG_C and $bitmask & FLAG_D ); // False, C and D are NOT BOTH set. echo (bool)( ( $bitmask & (FLAG_C | FLAG_D) ) ^ (FLAG_C | FLAG_D) ); // True, if C and D are NOT BOTH set.
// Resetting single flag. $bitmask &= $bitmask ^ FLAG_B; // Unsets B $bitmask &= $bitmask ^ FLAG_A; // A remains unset. var_dump( $bitmask ); // Only C still set (=4)
// Resetting multiple flags. $bitmask &= $bitmask ^ ( FLAG_C | FLAG_D ); // Unsets C and/or D var_dump( $bitmask ); // No flags set (=0)
More referencing this for myself than anything... if you need to iterate through every possible binary combination where $n number of flags are set to 1 in a mask of $bits length:
<?php
echo masksOf(3,10);
function masksOf($n,$bits) {
$u = pow(2,$bits)-1; //start value, full flags on.
$masks = array();
while ($u>0) {
$z = numflags($u);
if ($z==$n) array_push($masks,$u);
$u--;
}
return ($masks);
}
The reason is that all binary numbers are treated as 32 bits, even if you've manually entered less. In order to get the result I expected (01), it was necessary to AND the result with the number of bits I wanted: in this case, 2 (the number 3, in decimal). Be aware that all return values will have zeros removed from the left until they reach a bit that is set to 1. Continuing the above example, the following:
Note that the actual value was a string of 31 zeros followed by a 1, but the zeros were not shown. This is probably a good thing.
Furthermore, the NOT operator uses two's complement, which means the number you get may be even stranger than you expect: using two's complement means that ~2 == -3. There are plenty of good explanations of two's complement online, so I won't go into that question here.
If what you want is just to reverse a string of bits without any interpretation, you can use a function like this:
It takes a binary string of any length, reverses the bits, and returns the new string. You can then treat it as a binary number, use bindec() to turn it into a decimal, or whatever you want.
I hope this helps someone as much as it would have helped me a week ago!
The NOT or complement operator ( ~ ) and negative binary numbers can be confusing.
~2 = -3 because you use the formula ~x = -x - 1 The bitwise complement of a decimal number is the negation of the number minus 1.
NOTE: just using 4 bits here for the examples below but in reality PHP uses 32 bits.
Converting a negative decimal number (ie: -3) into binary takes 3 steps: 1) convert the positive version of the decimal number into binary (ie: 3 = 0011) 2) flips the bits (ie: 0011 becomes 1100) 3) add 1 (ie: 1100 + 0001 = 1101)
You might be wondering how does 1101 = -3. Well PHP uses the method "2's complement" to render negative binary numbers. If the left most bit is a 1 then the binary number is negative and you flip the bits and add 1. If it is 0 then it is positive and you don't have to do anything. So 0010 would be a positive 2. If it is 1101, it is negative and you flip the bits to get 0010. Add 1 and you get 0011 which equals -3.
It is true that if both the left-hand and right-hand parameters are strings, the bitwise operator will operate on the characters' ASCII values. However, a complement is necessary to complete this sentence. It is not irrelevant to point out that the decimal character's ASCII value have different binary values.
<?php if (('18' & '32') == '10') { echo ord('18'); //return decimal value 49, which have binary value 110001 echo ord('32'); //return decimal value 51, which have binary value 110011 echo ord('10'); //return decimal value 49, which have binary value 110001 //Therefore 110001 & 110011 = 110001 } ?>
Say... you really want to have say... more than 31 bits available to you in your happy bitmask. And you don't want to use floats. So, one solution would to have an array of bitmasks, that are accessed through some kind of interface.
Here is my solution for this: A class to store an array of integers being the bitmasks. It can hold up to 66571993087 bits, and frees up unused bitmasks when there are no bits being stored in them.
<?php /* Infinite* bits and bit handling in general.
*Not infinite, sorry.
Perceivably, the only limit to the bitmask class in storing bits would be the maximum limit of the index number, on 32 bit integer systems 2^31 - 1, so 2^31 * 31 - 1 = 66571993087 bits, assuming floats are 64 bit or something. I'm sure that's enough enough bits for anything.. I hope :D. */
DEFINE('INTEGER_LENGTH',31); // Stupid signed bit.
class bitmask { protected $bitmask = array();
public function set( $bit ) // Set some bit { $key = (int) ($bit / INTEGER_LENGTH); $bit = (int) fmod($bit,INTEGER_LENGTH); $this->bitmask[$key] |= 1 << $bit; }
public function remove( $bit ) // Remove some 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 ) // Toggle some 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 ) // Read some bit { $key = (int) ($bit / INTEGER_LENGTH); $bit = (int) fmod($bit,INTEGER_LENGTH); return $this->bitmask[$key] & (1 << $bit); }
public function stringin($string) // Read a string of bits that can be up to the maximum amount of bits long. { $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() // Print out a string of your nice little bits { $string = "";
As an additional curiosity, for some reason the result of the operation ("18" & "32") is "10". In other words, try avoiding using the binary operators on strings :)
After attempting to translate a Perl module into PHP, I realized that Perl's implementation of the ^ operator is different than the PHP implementation. By default, Perl treats the variables as floats and PHP as integers. I was able to verify the PHP use of the operator by stating "use integer;" within the Perl module, which output the exact same result as PHP was using.
The logical decision would be to cast every variable as (float) when using the ^ operator in PHP. However, this will not yield the same results. After about a half hour of banging my head against the wall, I discovered a gem and wrote a function using the binary-decimal conversions in PHP.
/* not having much experience with bitwise operations, I cannot tell you that this is the BEST solution, but it certainly is a solution that finally works and always returns the EXACT same result Perl provides. */ function binxor($a, $b) { return bindec(decbin((float)$a ^ (float)$b)); }
//normal PHP code will not yeild the same result as Perl $result = 3851235679 ^ 43814; //= -443704711
//to get the same result as Perl $result = binxor(3851235679, 43814); //= 3851262585 //YIPPEE!!!
//to see the differences, try the following $a = 3851235679 XOR 43814; $b = 3851235679 ^ 43814; //integer result $c = (float)3851235679 ^ (float)43814; //same as $b $d = binxor(3851235679, 43814); //same as Perl!!
For those who are looking for a circular bit shift function in PHP (especially useful for cryptographic functions) that works with negtive values, here is a little function I wrote:
(Note: It took me almost a whole day to get this to work with negative $num values (I couldn't figure out why it sometimes worked and other times didn't), because PHP only has an arithmatic and not a logical bitwise right shift like I am used to. I.e. 0x80000001>>16 will ouputs (in binary) "1111 1111 1111 1111 1000 0000 0000 0000" instead of "0000 0000 0000 0000 1000 0000 0000 0000" like you would expect. To fix this you have to apply the mask (by bitwise &) equal to 0x7FFFFFFF right shifted one less than the offset you are shifting by.)
<?php function circular_shift($num,$offset) { //Do a nondestructive circular bitwise shift, if offset positive shift left, if negative shift right $num=(int)$num; $mask=0x7fffffff; //Mask to cater for the fact that PHP only does arithmatic right shifts and not logical i.e. PHP doesn't give expected output when right shifting negative values 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; } ?>
To make very clear why ("18" & "32") is "10". 1) they they are both strings , 2) "&" operator works on strings by taking each !Character! from each string and make a bit wise & between them and add this value to the resulting string
So: "18" is made up of two characters: 0x31, 0x38 "32" is made up of two characters: 0x33, 0x32 ----RESULT----- 0x31 & 0x33 = 0x31 => "1" 0x38 & 0x32 = 0x30 => "0"
Just a note regarding negative shift values, as the documentation states each shift is an integer multiply or divide (left or right respectively) by 2. That means a negative shift value (the right hand operand) effects the sign of the shift and NOT the direction of the shift as I would have expected. FE. 0xff >> -2 results in 0x0 and 0xff << -2 result in 0xFFFFFFFFC0000000 (dependant on PHP_INT_MAX)
This seems rather inconsistent behavior. An integer XOR'd with zero results the original integer. But a string XOR'd with an empty value results an empty value!
My password hashing function was always returning the same hash... Because I was XOR-ing it with a salt that was sometimes empty!
Here is an easy way to use bitwise operation for 'flag' functionality. By this I mean managing a set of options which can either be ON or OFF, where zero or more of these options may be set and each option may only be set once. (If you are familiar with MySQL, think 'set' datatype). Note: to older programmers, this will be obvious.
Here is the code: <?php function set_bitflag(/*variable-length args*/) { $val = 0; foreach(func_get_args() as $flag) $val = $val | $flag; return $val; } function is_bitflag_set($val, $flag) { return (($val & $flag) === $flag); } // Define your flags define('MYFLAGONE', 1); // 0001 define('MYFLAGTWO', 2); // 0010 define('MYFLAGTHREE', 4); // 0100 define('MYFLAGFOUR', 8); // 1000 ?>
I should point out: your flags are stored in a single integer. You can store loads of flags in a single integer.
To use my functions, say you wanted to set MYFLAGONE and MYFLAGTHREE, you would use: <?php $myflags = set_bitflags(MYFLAGONE, MYFLAGTHREE); ?> Note: you can pass set_bitflags() as many flags to set as you want.
When you want to test later if a certain flag is set, use e.g.: <?php if(is_bitflag_set($myflags, MYFLAGTWO)) { echo "MYFLAGTWO is set!"; } ?>
The only tricky part is defining your flags. Here is the process: 1. Write a list of your flags 2. Count them 3. Define the last flag in your list as 1 times 2 to the power of <count> minus one. ( I.E. 1*2^(<count>-1) ) 3. Working backwards through your list, from the last to the first, define each one as half of the previous one. You should reach 1 when you get to the first