PHP right shifting and negative result? - php

I've run into a problem whilst converting some C code to PHP, specifically in the use of the right-shift operator.
edit: in the following examples, bit = 0;
Original C code:
p->param->outBits[bytePtr++] |= codeword >> (9 + bit);
PHP code:
$outBits[$bytePtr++] |= $codeword >> (9 + $bit);
If I start with codeword being 130728, in C I get the expected result of -1. In PHP I get 255. I understand this is something to do with arithmetic/logical shift differences, and the negative sign not being introduced as a result of the MSBs staying at zero.
Is there a "quick" way of doing the above in PHP that doesn't involve the shifting? eg via basic arithmetic or similar, that will give me the expected answer?

Your problem is that PHP doesn't have a type byte, it only has integer which usually is 32 bits (not 8), so if you really need negative value there (the bits are correct anyway, because unsigned 255 is the same as signed -1), then you should probably add the missing 24 ones or use arithmetics to restore the negative value (255 is -1, 254 is -2 and so on i.e. 256 - x = -x).

Related

PHP's Infinit value in Bitwise Operations returns strange values

Today I just made an interesting discovery while testing what happens calculating bitwisely in php like INF ^ 0 (^ => Bitwise Operator for Exclusive OR (XOR)) what gave me int(-9223372036854775808) => greatest possible negative value in a 64-Bit system.
But then I was asking myself: "Why is the result going negative in XOR when the "positive infinit" means 9223372036854775807 (63 Bits on 1 with a leading 0) and 0 (64 Bits on 0 => 0 xor 0 = 0) What is PHP's infinit value though and what is the calculation behind it? And why do I get a (correct?) negative value when I use "negative infinit"(A leading 1 against a leading 0 on 0 => 1 xor 0 = 1?".
Another interesting point is that this just happens on PHP Version 5.5.9-1, and not e.g. on 5.3.x. and 5.6.x (where i've tested it)! Maybe someone has an idea what happens there? Tested it on three versions but just mine (5.5.9-1) gives those results:
Just to let you guys know, it's just an abstract playaround i've done for fun but I find it's interesting. Maybe someone can help here or explain me a wrong thought I have? Just tell me if someone needs more informations about anything!
EDIT: Accordingly to jbafford it would be great to get a complete answere, so i'll just quote him: why does 5.5 and 5.6 result in PHP_INT_MIN, and everything else return 0?
First off, ^ itself isn't what's special here. If you XOR anything with zero, or OR anything with zero, you just get back the original answer. What you're seeing here is not part of the operation itself, but rather what happens before the operation: the bitwise operators take integers, so PHP converts the float to an integer. It's in the float-to-integer conversion that the weird behaviour appears, and it's not exclusive to the bitwise operators. It also happens for (int), for example.
Why does it produce these weird results? Simply because that's what the C code PHP is written in produces when converting a float to an integer. In the C standard, C's behaviour for float-to-integer conversions is undefined for the special values of INF, -INF and NAN (or, more accurately, for "integral parts" an integer can't represent: ยง6.3.1.4). This undefined behaviour means the compiler is free to do whatever it wants. It just so happens in this case that the code it generates produces the minimum integer value here, but there's no guarantee that will always happen, and it's not consistent across platforms or compilers.1 Why did the behaviour change between 5.4 and 5.5? Because PHP's code for converting floats to integers changed to always perform a modulo conversion. This fixed the undefined behaviour for very large floating-point numbers,2 but it still didn't check for special values, so for that case it still produced undefined behaviour, just slightly different this time.
In PHP 7, I decided to clean up this part of PHP's behaviour with the Integer Semantics RFC, which makes PHP check for the special values (INF, -INF and NAN) and convert them consistently: they always convert to integer 0. There's no longer undefined behaviour at work here.
1 For example, a test program I wrote in C to try to convert Infinity to an integer (specifically a C long) has different results on 32-bit and 64-bit builds. The 64-bit build always produces -9223372036854775808, the minimum integer value, while the 32-bit build always produces 0. This behaviour is the same for GCC and clang, so I guess they're both producing very similar machine code.
2 If you tried to convert a float to an integer, and that float's value was too big to fit in an integer (e.g. PHP_INT_MAX * 2, or PHP_INT_MIN * 2), the result was undefined. PHP 5.5 makes the result consistent, though unintuitive (it acts if the float was converted to a very large integer, and the most significant bits were discarded).
Your float(INF) gets implicitly casted to an Integer.
and XOR with 0 does not change the first parameter. So basically this is just a cast from float to int which is undefined for values which are not in the integer range. (for all other values it will be truncated towards zero)
https://3v4l.org/52bA5

How to use a 32bit integer on a 64bit installation of PHP?

I run into a problem on my code when moved to production because the production server is 32bit while my development machine is 64bit (I'm running Kubuntu 12.04 64bit). My question is. Is it possible to force an int to be 32bit without installing the 23bit version of PHP? Either that or a library that allow me to chose the max int value
Integers are the size of pointers on that platform. (32-bit PHP --> 32-bit integers. 64-bit PHP --> 64-bit integers).
Note that when integer operations overflow, the variables become floats. The PHP documentation explains all of this well.
I'm not sure what you're doing in your code that would cause you to care what size the integer is though. If you only care about 32-bits of a value, however, you can always mask off the low 32 bits:
$val = $something & 0xFFFFFFFF;
$r = $r & 0xFFFFFFFF;
if ($r & 0x80000000)
{
$r = $r & ~0x80000000;
$r = -2147483648 + $r;
}
$r = $r & 0xFFFFFFFF;
if ($r & 0x80000000)
{
$r = $r & ~0x80000000;
$r = -2147483648 + $r;
}
Trying to give an explanation for who requested it.
Take your $r big number variable (let's say spread on 64 bits) and let only the 32 most right bits to pass.
$r = $r & 0xFFFFFFFF; //aka 0x00000000FFFFFFFF
Now 64bits $r variable has 32 zeros and your 32 bits. Note that conventionally the most left bit is the sign, in both 64 and 32 bits representation: plus (0) or minus (1), so 64bits $r is now considered positive by PHP.
if ($r & 0x80000000) {
"32 zeros, your 32 bits" ANDed with "32 zeros, 1 and 31 zeros": if this gives true, meaning there is at least (at most) one 1, which again means: "in the 32bit world, this is a negative number", in this case then we need to also tell PHP that this is a negative number, because most left bit of the 64bits $r variable is still 0 and PHP still thinks is a positive number.
So, let's tell PHP this is actually a negative number and rebase the number. First step, set to zero just the most left of your 32 bits to force the positiveness of your 32bits.
$r = $r & ~0x80000000;
~0x80000000 indeed can be rewritten as 0xFFFFFFFF7FFFFFFF: 32 ones, 1 zero, 31 ones.
Now you have a positive 32bit number representation inside a 64bit variable and it is complementary of it's related 32bit negative representation: e.g. if you had 64 ones in the very beginning (-1 in decimal), at this point $r is 32 zeros, 1 zero you just set, 31 ones (2147483647 in decimal).
You should now see the point:
$r = -2147483648 + $r;
Minimum representable 32bit number + our complementary positive number.
At this point, $r variable is still a 64bit variable, but it now correctly represents the negativiness of the 32 most right bits you chose in the beginning.
The whole problem is that in 64bit PHP, if you extract the most right 32 bits and you fill another 64bits variable with, the value is still positive because the most left of the 64bits is 0. What we wanted to achieve instead: making the 64bits variable negative, if the extracted 32bits represent a negative number; a 64bits variable positive, if the extracted 32bits represent a positive number.

Find the highest set bit

I have 5 different values which are saved as bits like 10010.
I get the value as an Int from the Database (cannot change that) so like 24 means 11000
I know that i can get the biggest bit here by using
if ((decbin($d) & 16) == 16)
but if the first is 0 i would have to check the next bit, and if that is 0 i would have to ...
So after all i would have a block of ifs and if there were more bits the block was bigger.
Is there a simple way to just get the "id" (or value, would not matter) of the highest bit with a 1?
Yes. Compute the base 2 logarithm of the number and floor it:
$highbit = floor(log($d, 2));
If $highbit, for instance, is 5, it means that the 5th bit is the highest bit set to 1.
The highest bit set in an integer is equal to the integer base-2 logarithm of that integer.
While there exist many different implementations for doing that kind of thing in assembler and C and such, more or less efficiently, the probably easiest way to do it in PHP is to actually use logarithm.
The log() function surely is not the most efficient way of solving the problem, but seeing as you are in a script language, it probably won't be any slower (and quite possibly faster) than implementing one of the "better" algorithms in PHP with 2 dozen statements.
Thus:
$highestbit = (int)(log($value,2));
Interesting ... had to check for rounding-problems immediately and found none in the range of up to 1'000'000, though my test-code revealed problems for less native bases, like 3:
3^5 = 243 but floor(log(243, 3)) gives 4
def hibit(v):
""" uint v -> highest bit: 0101 -> 0100, 01xxxx -> 010000 """
# cf http://graphics.stanford.edu/~seander/bithacks.html
v |= v >> 1
v |= v >> 2
v |= v >> 4
v |= v >> 8
v |= v >> 16
return v ^ (v >> 1)
for v in range(0, 9+1) + range(2**31-1, 2**31+2):
print v, hibit(v)

GMP Bit shift doesn't work on negative numbers

I found this function at php.net. It seems to work on positive numbers, but fails on negative ones:
function gmp_shiftr($x,$n) { // shift right
return(gmp_div($x,gmp_pow(2,$n)));
}
echo -1 >> 8; //returns -1, presumably correctly
echo "<br />";
echo gmp_strval(gmp_shiftr(-1,8)); //returns 0, presumably incorrectly
How could I fix up the function to work with negatives?
Two ideas I have:
Maybe I could do something along the lines of
if (whatever) { $a >> $b} else{ gmp_shiftr($a, $b) }?
Or, maybe I could subtract something from the negative results depending on their value..?
I just want to get the value that >> would give, but also get it for >32bit numbers when I use GMP.
Looking at the GMP documentation for the division routines, there's a function
void mpz_tdiv_q_2exp (mpz_t q, mpz_t n, unsigned long int b)
that seems like it might be what you want: an arithmetic right shift that treats
n as if it were represented in twos-complement, and (I think) shifts it b places
to the right. Unfortunately, that level of the API doesn't seem to be exposed by PHP GMP.
I found a bit twiddling hack for doing sign extension when the number of bits
in the representation is unknown:
unsigned b; // number of bits representing the number in x
int x; // sign extend this b-bit number to r
int r; // resulting sign-extended number
int const m = 1U << (b - 1); // mask can be pre-computed if b is fixed
x = x & ((1U << b) - 1); // (Skip this if bits in x above position b are already zero.)
r = (x ^ m) - m;
Since bitwise AND and XOR are supported by PHP GMP, you might be able to make
this work...
If you think about this mathematically it makes sense. gmp_shiftr is doing -1/256, which, when rounding towards zero (the gmp default) is 0.
The ">>" method works like it does because negative numbers are represented in sign-extended twos complement form.

PHP bitwise left shifting 32 spaces problem and bad results with large numbers arithmetic operations

I have the following problems:
First: I am trying to do a 32-spaces bitwise left shift on a large number, and for some reason the number is always returned as-is. For example:
echo(516103988<<32); // echoes 516103988
Because shifting the bits to the left one space is the equivalent of multiplying by 2, i tried multiplying the number by 2^32, and it works, it returns 2216649749795176448.
Second: I have to add 9379 to the number from the above point:
printf('%0.0f', 2216649749795176448 + 9379); // prints 2216649749795185920
Should print: 2216649749795185827
Doing 32 bit-shifting operations will probably not work like you expect, as integers tend to be stored on 32 bits.
Quoting this page : Bitwise Operators
Don't right shift for more than 32
bits on 32 bits systems. Don't left
shift in case it results to number
longer than 32 bits. Use functions
from the gmp extension for bitwise
manipulation on numbers beyond
PHP_INT_MAX.
Php integer precision is limited to machine word size (32, 64). To work with arbitrary precision integers you have to store them as strings and use bc or gmp library:
echo bcmul('516103988', bcpow(2, 32)); // 2216649749795176448
Based on Pascal MARTIN's suggestions, i tried both the BCMath and the GMP extension and came up with the following solutions:
With BCMath:
$a = 516103988;
$s = bcpow(2, 32);
$a = bcadd(bcmul($a, $s), 9379);
echo $a; // works, echoes 2216649749795185827
With GMP:
$a = gmp_init(516103988);
$s = gmp_pow(gmp_init(2), 32);
$a = gmp_add(gmp_mul($a, $s), gmp_init(9379));
echo gmp_strval($a); // also works
From what i understand, there is a far greater chance for BCMath to be installed on the server then GMP, so i will be using the first solution.
Thanks :)

Categories