C & PHP: Storing settings in an integer using bitwise operators? - php

I'm not familiar with bitwise operators, but I have seem them used to store simple settings before.
I need to pass several on/off options to a function, and I'd like to use a single integer for this. How can I go about setting and reading these options?

You sure can do it in PHP.
Let's say you have four booleans you want to store in a single value. That means we need four bits of storage space
0000
Each bit, when set individually, has a unique representation in decimal
0001 = 1 // or 2^0
0010 = 2 // or 2^1
0100 = 4 // or 2^2
1000 = 8 // or 2^3
A common way to implement this is with bit masks to represent each option. PHP's error levels are done this way, for example.
define( 'OPT_1', 1 );
define( 'OPT_2', 2 );
define( 'OPT_3', 4 );
define( 'OPT_4', 8 );
Then when you have an integer that represents 0 or more of these flags, you check with with the bitwise and operator which is &
$options = bindec( '0101' );
// can also be set like this
// $options = OPT_1 | OPT_3;
if ( $options & OPT_3 )
{
// option 3 is enabled
}
This operator works as such: only bits that are set in both operands are set in the result
0101 // our options
0100 // the value of OPT_3
----
0100 // The decimal integer 4 evaluates as "true" in an expression
If we checked it against OPT_2, then the result would look like this
0101 // our options
0010 // the value of OPT_2
----
0000 // The decimal integer 0 evaluates as "false" in an expression

It works pretty much the same way in both languages, a side by side comparison:
C:
#include <stdio.h>
#include <stdint.h>
#define FLAG_ONE 0x0001
#define FLAG_TWO 0x0002
#define FLAG_THREE 0x0004
#define FLAG_FOUR 0x0008
#define FLAG_ALL (FLAG_ONE|FLAG_TWO|FLAG_THREE|FLAG_FOUR)
void make_waffles(void)
{
printf("Yummy! We Love Waffles!!!\n");
}
void do_something(uint32_t flags)
{
if (flags & FLAG_TWO)
make_waffles();
}
int main(void)
{
uint32_t flags;
flags |= FLAG_ALL;
/* Lets make some waffles! */
do_something(flags);
return 0;
}
PHP:
<?php
define("FLAG_ONE", 0x0001);
define("FLAG_TWO", 0x0002);
define("FLAG_THREE", 0x0004);
define("FLAG_FOUR", 0x0008);
define("FLAG_ALL", FLAG_ONE|FLAG_TWO|FLAG_THREE|FLAG_FOUR);
function make_waffles()
{
echo 'Yummy! We Love Waffles!!!';
}
function do_something($flags)
{
if ($flags & FLAG_TWO)
make_waffles();
}
$flags |= FLAG_TWO;
do_something($flags);
?>
Note, you don't absolutely need to use constants, I just use them out of habit. Both examples will run, I compiled the C version via gcc -Wall flags.c -o flags. Change flags in either example to anything but FLAG_TWO or FLAG_ALL and (sadly) no waffles will be made.
In the C version, you don't have to tickle the preprocessor, it could quite easily be an enum, etc - that's an exercise for the reader.

quote
"the idea is not good, really. you would better pass few boolean. if you want use bitwise then
function someFunc($options)
{
if ($options & 1 != 0)
//then option 1 enabled
if ($options & (1 << 1) != 0)
//then option 2 enabled
if ($options & (1 << 2) != 0)
//then option 3 enabled
}
"
What you have done would be okay if you were checking for a single value, although not optimal, so checking that a bit is enabled, but lets say we wanted to be able to match any, or exact we could have the following methods
function matchExact($in, $match) { // meets your criterion, as would a switch, case, but ultimately not suited for use with flags
return $in === $match;
}
function matchAny($in, $match) { // meets original criterion with more lexical name however it returns true if any of the flags are true
return $in |= $match;
}
if you then wanted to expand upon this by having specific actions only happening if bit x,y,z was enabled then you could use the following
function matchHas($in, $match) { // more bitwise than === as allows you to conditionally branch upon specific bits being set
return $in &= $match;
}
I also think if you are doing what was done in the above quote, flags may not be the best idea, exact values might be better, which does have the benefit of allowing more discreet actions. (0-255) for 8-bit over 8 distinct flags
The whole reason flags work so well is because in base 2 "8" does not contain "4", and "2" does not contain "1".
________________________
|8|4|2|1|Base 10 Value |
------------------------
|1|1|1|1|15 |
|1|1|1|0|14 |
|1|1|0|1|13 |
|1|1|0|0|12 |
|1|0|1|1|11 |
|1|0|1|0|10 |
|1|0|0|1|9 |
|1|0|0|0|8 |
|0|1|1|1|7 |
|0|1|1|0|6 |
|0|1|0|1|5 |
|0|1|0|0|4 |
|0|0|1|1|3 |
|0|0|1|0|2 |
|0|0|0|1|1 |
|0|0|0|0|0 |
------------------------

Related

Bitwise Compressed Booleans - Toggling Specific Ones

Background
I have to store a number of true/false values in a database, and rather than make many columns, I instead am using a single int column, where I store all of the booleans, compressed using Bitwise operators.
Example:
Product 1 is certified as:
Lead Free - Yes
Gluton Free - No
Free Range - Yes
Organic - Yes
So in the DB, in the certs column, I'd compress 1, 0, 1, 1 into 13 (1 + 4 + 8).
I've written quick wrapper functions to compress and extract this information (change from int into boolean array and back).
Problem
I'm not sure of the best way to quickly add and remove values from this. Say I want to update the product to NOT be Free Range anymore. I can take the compressed int, minus 4. That works. Except what if it already wasn't Free Range? If I'm doing bulk operations, I need my function to only remove 4 from products that are currently certified Free Range, or the number gets messed up.
I have figured it out with a lengthy if statement, but it's not elegant. This works:
// $certs_current is the compressed int from the DB.
// $certs_new is a new integer. 4 would mean toggle "Free Range" to true. -4 would mean toggle it to false.
if ( $certs_current & abs($certs_new) ) {
if ( $certs_new < 0 ) {
$certs_current += $certs_new;
}
} else {
if ( $certs_new > 0 ) {
$certs_current += $certs_new;
}
}
This is a lot of if statements. I played around with the | operator, but it only works for adding positive numbers. Pass it a -4 and it breaks. And I can't figure out a nor operator.
Is there a better way to do this?
Edit:
In other words, is there an operator where: If I give it 13 and -4, it'll give me 9. But if I give it 9 and -4, it'll give 9? Or even just 13 and 4.
Edit 2
Adding in the | to my working logic, I've reduced the complexity and still have it working:
if ( $certs_new > 0 ) {
$certs_current = ( $certs_current | $certs_new );
} else {
if ( $certs_current & abs($certs_new) ) {
$certs_current += $certs_new;
}
}
Basically it says, if the new number is positive, then use the | operator to add it if it needs to be added. If it's negative, then we first check to see if we need to remove it, and if so, remove it. Which is the place where I think we can improve.
The | replaces an if statement, because it only adds the 4 if the 4 isn't already toggled to true (I realized that's a weird way to describe it).
But with the negative number, I still have a nested if statement, which is what I want to remove if possible.
You have the compressed value, so why don't you expand it before doing your operations?
$certs_temp = $certs_current
$is_organic = $certs_temp >= 8 && $certs_temp -= 8
$is_freerange = $certs_temp >= 4 && $certs_temp -= 4
$is_glutenfree = $certs_temp >= 2 && $certs_temp -= 2
$is_leadfree = $certs_temp >= 1 && $certs_temp -= 1
And then re-compress with the changed values (if indeed anything has changed).
I think you are asking for trouble if you try to operate directly on the stored integer, which actually represents 4 separate binary flags. Either represent each flag separately in the DB, or if you must have it stored as an integer, then decode it before applying changes or logic.
Answer
I figured it out. To toggle a boolean to false, we need to:
Reverse the primary compressed int from the DB, using ~ (13 in the example)
Add in the absolute value of the new int 4 ( not -4), using |.
Re-reverse the full int again, using ~.
So the final code, with only one if statement:
if ( $certs_new > 0 ) {
$certs = ( $certs | $certs_new );
} else {
$certs = ~( ~$certs | abs($certs_new) );
}
// 13 and 4 .... 13
// 13 and 2 .... 15
// 13 and -4 .... 9
// 13 and -2 .... 13

Laravel give data Type bit/byte in migration

Is there any database similiar to bit (byte) in laravel, could not find any in the documentation. (https://laravel.com/docs/5.1/migrations).
I try to do something like:
00000001 -> stands for something lets say playstation
00000010 -> stands for xbox
00000011 -> stands for both above
Instead of trying to use the BIT datatype, which would be a bit of a hassle to work with, you can just use an integer and bitwise operators instead, assuming you don't need more than 32 options for one field (bigint = 8 bytes = 32 bits).
As a very simple example, imagine you create the following enum class:
class Bitmask {
const ATARI = 1 << 0; // 00000001 (1)
const NES = 1 << 1; // 00000010 (2)
const SNES = 1 << 2; // 00000100 (4)
const SEGA = 1 << 3; // 00001000 (8)
const PLAYSTATION = 1 << 4; // 00010000 (16)
const XBOX = 1 << 5; // 00100000 (32)
}
To set the field, all you need to do is add the bitmasks together (in this configuration, ORing (|) them is the same). If a user has an NES and a PLAYSTATION:
$user->systems = Bitmask::NES + Bitmask::PLAYSTATION;
To query, you would use the bitwise operators. So, if you wanted the users that have SEGAs:
User::where('systems', '&', Bitmask::SEGA)->get();
If you wanted the users that have PLAYSTATIONs or XBOXes:
User::where('systems', '&', Bitmask::PLAYSTATION | Bitmask::XBOX)->get();
The & operator will perform a bitwise AND operation between the integer field and the integer you pass in. If any of the bits match up, the & operator will return a value > 0, and the where clause will be truthy. If none of the bits match up, the & operator will return 0 and the where clause will be false.

Bitwise AND operator doesn't work as expected

I use binary code to handle my overwrites in my database, but now I need to lookup about the rules are match or not match to this i have follow rules.
1 = unlock title
2 = unlock desc
4 = unlock price
8 = unlock stock
When I use php I try to use it like MySQL match:
$overwirtes = 5;
if ( decbin($overwirtes) & decbin(1) )
{
// unlock title
}
if ( decbin($overwirtes) & decbin(2) )
{
// unlock desc
}
if ( decbin($overwirtes) & decbin(4) )
{
// unlock price
}
if ( decbin($overwirtes) & decbin(8) )
{
// unlock stock
}
What I expect it's title and price are unlock and desc and stock are lock, but something go wrong and php will not accept binary like MySQL, can somebody tell me what I do wrong here, I'm still new to work on Binary code as rules.
You run into a "funny" problem. And this is that decbin() returns a string.
Now if both operands of the bitwise AND operator are strings the operation is preformed with the ASCII values of the strings.
Also a quote from the manual which shows this too:
If both operands for the &, | and ^ operators are strings, then the operation will be performed on the ASCII values of the characters that make up the strings and the result will be a string. In all other cases, both operands will be converted to integers and the result will be an integer.
So what does that mean in your specific example?
Let's take the second if statement:
$overwirtes = 5;
decbin($overwirtes) & decbin(2)
Well technical this should be evaluated as followed:
0000 0101 (5)
0000 0010 (2)
---------- &
0000 0000 = 0 (FALSE)
But since both operands are string the bitwise AND takes the ASCII values of both strings which here would be this:
0011 0101 (53)
0011 0010 (50)
---------- &
0011 0000 = "48" (TRUE)
So that's why all conditions in your code evaluates as TRUE.
But now how to solve this? Simple, just change 1 operand of the operation to an integer. So you can just remove the decbin() call from 1, 2, 4, 8.
You can also then see in the manual(quote above) when 1 operand isn't a string (here an integer) that both operands gets implicit casted to an integer. And you also receive an integer.
So your code should look something like this:
$overwirtes = 5;
if ( decbin($overwirtes) & 1)
{
// unlock title
}
if ( decbin($overwirtes) & 2)
{
// unlock desc
}
if ( decbin($overwirtes) & 4)
{
// unlock price
}
if ( decbin($overwirtes) & 8)
{
// unlock stock
}

How To Test PHP Bitwise Function Input Parameters

Sometimes in programming they allow one to chain parameters in a single function input variable like the second input variable below:
define('FLAGA',40);
define('FLAGB',10);
define('FLAGC',3);
function foo($sFile, $vFlags) {
// do something
}
foo('test.txt',FLAGA | FLAGB | FLAGC);
PHP calls this single pipe character (|) the Bitwise OR operator. How do I now add something inside foo() to test $vFlags to see which flags were set?
I think you'll find that flags like this are normally defined as powers of 2, e.g.:
define('FLAGA',1);
define('FLAGB',2);
define('FLAGC',4); /* then 8, 16, 32, etc... */
As you rightly stated, these can be combined by using a bitwise OR operator:
foo('test.txt',FLAGA | FLAGB | FLAGC);
To test these flags inside your function, you need to use a bitwise AND operator as follows:
function foo($sFile, $vFlags) {
if ($vFlags & FLAGA) {
// FLAGA was set
}
if ($vFlags & FLAGB) {
// FLAGB was set
}
//// etc...
}
The "flags" parameter would be called a bitmask. A single byte contains 8 bits which are either set or not set. You simply assign your own meaning to every bit; if it's set it means yes for that particular bit, otherwise no.
So you need to start by defining your flags with the correct values which set the right bits; just arbitrary numbers won't combine together in the right ways:
define('FLAGA', 1); // 00000001
define('FLAGB', 2); // 00000010
define('FLAGC', 4); // 00000100
define('FLAGD', 8); // 00001000
Given the above, FLAGB | FLAGD creates a bit mask with the second and forth bit set (00001010). You need to get somewhat comfortable with converting between base 2 (binary) and base 10 (decimal) for this.
To test for this, you use &:
$flags = FLAGB | FLAGD;
if ($flags & FLAGA) {
echo 'flag A is set';
}
if ($flags & FLAGB) {
echo 'flag B is set';
}
..
You need the bitwise AND operator &:
define('FLAGA',40);
define('FLAGB',10);
define('FLAGC',3);
function foo($sFile, $vFlags) {
if ($vFlags & FLAGA) {
echo "FLAGA is set\n";
}
if ($vFlags & FLAGB) {
echo "FLAGB is set\n";
}
if ($vFlags & FLAGC) {
echo "FLAGC is set\n";
}
}
foo('test.txt',FLAGA | FLAGB | FLAGC);
DEMO
That said, bitwise operations necessarily work in terms of binary, where each bit represents a power of 2. So, you are typically going to want to define flags in powers of 2, as in
define('FLAGA',1);
define('FLAGB',2);
define('FLAGC',4);
define('FLAGD',8); // etc.
Otherwise, imagine this scenario:
define('FLAGA',8);
define('FLAGB',32);
define('FLAGC',40);
If you have a value 40 for $vFlags, you can't tell what flags are set; it could be any of the following:
FLAGA & FLAGB
FLAGA & FLAGB & FLAGC
FLAGA & FLAGC
FLAGB & FLAGC
FLAGC

Most efficient way to extract bit flags

I have these possible bit flags.
1, 2, 4, 8, 16, 64, 128, 256, 512, 2048, 4096, 16384, 32768, 65536
So each number is like a true/false statement on the server side. So if the first 3 items, and only the first 3 items are marked "true" on the server side, the web service will return a 7. Or if all 14 items above are true, I would still get a single number back from the web service which is is the sum of all those numbers.
What is the best way to handle the number I get back to find out which items are marked as "true"?
Use a bit masking operator. In the C language:
X & 8
is true, if the "8"s bit is set.
You can enumerate the bit masks, and count how many are set.
If it really is the case that the entire word contains bits, and you want to simply
compute how many bits are set, you want in essence a "population count". The absolute
fastest way to get a population count is to execute a native "popcnt" usually
available in your machine's instruction set.
If you don't care about space, you can set up a array countedbits[...] indexed by your value with precomputed bit counts. Then a single memory access computes your bit count.
Often used is just plain "bit twiddling code" that computes bit counts:
(Kernigan's method):
unsigned int v; // count the number of bits set in v
unsigned int c; // c accumulates the total bits set in v
for (c = 0; v; c++)
{
v &= v - 1; // clear the least significant bit set
}
(parallel bit summming, 32 bits)
v = v - ((v >> 1) & 0x55555555); // reuse input as temporary
v = (v & 0x33333333) + ((v >> 2) & 0x33333333); // temp
c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24; // count
If you haven't seen the bit twiddling hacks before, you're in for a treat.
PHP, being funny, may do funny things with some of this arithmetic.
if (7 & 1) { // if bit 1 is set in returned number (7)
}
Thought the question is old might help someone else. I am putting the numbers in binary as its clearer to understand. The code had not been tested but hope the logic is clear. The code is PHP specific.
define('FLAG_A', 0b10000000000000);
define('FLAG_B', 0b01000000000000);
define('FLAG_C', 0b00100000000000);
define('FLAG_D', 0b00010000000000);
define('FLAG_E', 0b00001000000000);
define('FLAG_F', 0b00000100000000);
define('FLAG_G', 0b00000010000000);
define('FLAG_H', 0b00000001000000);
define('FLAG_I', 0b00000000100000);
define('FLAG_J', 0b00000000010000);
define('FLAG_K', 0b00000000001000);
define('FLAG_L', 0b00000000000100);
define('FLAG_M', 0b00000000000010);
define('FLAG_N', 0b00000000000001);
function isFlagSet($Flag,$Setting,$All=false){
$setFlags = $Flag & $Setting;
if($setFlags and !$All) // at least one of the flags passed is set
return true;
else if($All and ($setFlags == $Flag)) // to check that all flags are set
return true;
else
return false;
}
Usage:
if(isFlagSet(FLAG_A,someSettingsVariable)) // eg: someSettingsVariable = 0b01100000000010
if(isFlagSet(FLAG_A | FLAG_F | FLAG_L,someSettingsVariable)) // to check if atleast one flag is set
if(isFlagSet(FLAG_A | FLAG_J | FLAG_M | FLAG_D,someSettingsVariable, TRUE)) // to check if all flags are set
One way would be to loop through your number, left-shifting it (ie divide by 2) and compare the first bit with 1 using the & operand.
As there is no definite answer with php code, I add this working example:
// returns array of numbers, so for 7 returns array(1,2,4), etc..
function get_bits($decimal) {
$scan = 1;
$result = array();
while ($decimal >= $scan){
if ($decimal & $scan) $result[] = $scan;
$scan<<=1;
}
return $result;
}

Categories