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.
Related
I stumbled on the difference between the result of Machine epsilon calculation.
When compared to 0 PHP yields 4.9406564584125E-324.
While for 1 it pops up with 1.1102230246252E-16.
Quite a difference. Guess it's something with the type of data initially set by default in PHP.
The code is:
<?php
//Machine epsilon calculation
$e = 1;
$eTmp = null;
for ($i = 0; 0 != 0 + $e; $i++){ //Changing 0 by 1 produces absolutely different result
$e = $e/2;
if ($e != 0) {$eTmp = $e;}
}
echo $eTmp;
//var_dump($eTmp);
?>
Any clarification on the difference between the two?
And how can a variable or value by assigned manually to float in PHP?
Many thanks for your ideas!
Your PHP implementation appear to be using the common IEEE 754 64-bit binary format. In this format, finite values are represented, effectively, as a sign, an integer M less than 253, and an integer exponent e between −1074 and 971, inclusive. The value represented is +M•2e or −M•2e, according to the sign. (This format will often be described with M being a fraction between 1 and 2 with a certain number of bits after the radix point. The descriptions are mathematically equivalent, just useful for different purposes.)
Given this, we can easily see that the next representable number after 0 is +1•2−1074, which is approximately 4.94065645841246544•10−324.
In this format as stated, 1 can be represented as +1•20. However, to see what the smallest change that can be made to 1 is, we must normalize it by making M as large as possible. 1 can also be represented with M = 252 and e = −52, resulting in +252•2−52. In this form, we can see the next representable value greater than 1 is achieved by adding 1 to M, mkaing the number +(252+1)•2−52.
The difference between 1 and +(252+1)•2−52 is of course 1•2−52, which is exactly 2.220446049250313080847263336181640625•10−16.
Note that your code records $eTmp (if $e is not zero) after reducing $e with $e = $e/2, which means it reports the first value of $e that does not cause a change. Thus it reports 1.11e-16 rather than 2.22e-16, which is the last value that does cause a change to 1.
I am currently trying to make a function that can tell if number a is closer to c than the number b is to c.
I have tried to do comparisons like so: (taking a and b from c and comparing those)
$c = 10;
$b = 2;
$a = 3;
$b_check = $c - $b; // = 8.
$a_check = $c - $a; // = 7.
In my head, I thought that whatever number (a or b) is smaller means that it's gonna be the closer number to c, though that worked using positive integers, but when it came to negative integers it gave the complete wrong outcome.
I was wondering if maybe there was an in-built function in PHP for this or if there's a better mathematical method for achieving this?
If (a - c) * (a - c) < (b - c) * (b - c) would do it. This gets round the negative number issue and is the way us old cats do it in C.
Else you could use Math.abs(a - c) < Math.abs(b - c) but the other way can be quicker for some types, but you need to take care not to overflow your type and there's a potential for a ruinous wraparound effect for some types too.
Profile it.
The one with the smallest absolute value is always the closest. You're thinking in terms of a number line, but it's easier to imagine if you know vectors in 2D or 3D space. Then the distance between two points is the square root of the sum of squares of the difference between their components. The 1D case falls out as a special case where two components are zero.
double distance = Math.sqrt((x2-x1)*(x2-x1)) = Math.abs(x2-x1)
I need to create a function which takes a single integer as argument in the range 0-N and returns a seemingly random number in the same range.
Each input number should always have exactly one output and it should always be the same.
Such a function would produce something like this:
f(1) = 4
f(2) = 1
f(3) = 5
f(4) = 2
f(5) = 3
I believe this could be accomplished by some kind of a hashing algorithm? I don't need anything complex, just not something too simple like f(1) = 2, f(2) = 3 etc.
The biggest issue is that I need this to be reversible. E.g. the above table should be true left-to-right as well as right-to-left, using a different function for the right-to-left conversion is fine.
I know the easiest way is to create an array, shuffle it and just store the relations in a db or something, but as I need N to be quite large I'd like to avoid this if possible.
Edit: For my particular case N is a specific number, it's exactly 16777216 (64^4).
If the range is always a power of two -- like [0,16777216) -- then you can use exclusive-or just as #MarkBaker suggested. It just doesn't work so easily if your range is not a power of two.
You can use addition and subtraction modulo N, although these alone are too obvious, so you have to combine it with something else.
You can also do multiplication modulo-N, but reversing that is complicated. To make it simpler, we can isolate the bottom eight bits and multiply those and add them in a way that doesn't interfere with those bits so we can use them again to reverse the operation.
I don't know PHP so I'm going to give an example in C, instead. Maybe it's the same.
int enc(int x) {
x = x + 4799 * 256 * (x % 256);
x = x + 8896843;
x = x ^ 4777277;
return (x + 1073741824) % 16777216;
}
And to decode, play the operations back in reverse order:
int dec(int x) {
x = x + 1073741824;
x = x ^ 4777277;
x = x - 8896843;
x = x - 4799 * 256 * (x % 256);
return x % 16777216;
}
That 1073741824 must be a multiple of N, and 256 must be a factor of N, and if N is not a power of two then you can't (necessarily) use exclusive-or (^ is exclusive-or in C and I assume in PHP too). The other numbers you can fiddle with, and add and remove stages, at your leisure.
The addition of 1073741824 in both functions is to ensure that x stays positive; this is so that the modulo operation doesn't ever give a negative result, even after we've subtracted values from x which might have made it go negative in the interim.
I offered to describe how I "randomly" scramble up 9-digit SSNs when producing research data sets. This does not replace or hash an SSN. It re-orders the digits. It is difficult to put the digits back in the correct order if you don't know the order in which they were scrambled. I have a gut feeling that this is not what the questioner really wants. So, I am happy to delete this answer if it is deemed off-topic.
I know that I have 9 digits. So, I start with an array that has 9 index values in order:
$a = array(0,1,2,3,4,5,6,7,8);
Now, I need to turn a key that I can remember into a way to shuffle the array. The shuffling has to be the same order for the same key every time. I use a couple tricks. I use crc32 to turn a word into a number. I use srand/rand to get a predictable order of random values. Note: mt_rand no longer produces the same sequence of random digits with the same seed, so I have to use rand.
srand(crc32("My secret key"));
usort($a, function($a, $b) { return rand(-1,1); });
The array $a still has the digits 0 through 8, but they are shuffled. If I use the same keyword I will get the same shuffled order every time. That lets me repeat this every month and get the same result. Then, with a shuffled array, I can pick the digits off the SSN. First, I ensure it has 9 characters (some SSNs are sent as integers and a leading 0 is omitted). Then, I build a masked SSN by picking the digits using $a.
$ssn = str_pad($ssn, 9, '0', STR_PAD_LEFT);
$masked_ssn = '';
foreach($a as $i) $masked_ssn.= $ssn{$i};
$masked_ssn will now have all the digits in $ssn, but in a different order. Technically, there are keywords that make $a become the original ordered array after shuffling, but that is very very rare.
Hopefully this makes sense. If so, you can do it all much faster. If you turn the original string into an array of characters, you can shuffle the array of characters. You just need to reseed rand every time.
$ssn = "111223333"; // Assume I'm using a proper 9-digit SSN
$a = str_split($ssn);
srand(crc32("My secret key"));
usort($a, function($a, $b) { return rand(-1,1); });
$masked_ssn = implode('', $a);
This is not really faster in a runtime way because rand is a rather expensive function and you run rand a hell of lot more here. If you are masking thousands of values as I do, you will want to use an index array that is shuffled just once, not a shuffling for every value.
Now, how do I undo it? Assume I'm using the first method with the index array. It will be something like $a = {5, 3, 6, 1, 0, 2, 7, 8, 4}. Those are the indexes for the original SSN in the masked order. So, I can easily build the original SSN.
$ssn = '000000000'; // I like to define all 9 characters before I start
foreach($a as $i=>$j) $ssn[$j] = $masked_ssn{$i};
As you can see, $i counts from 0 to 8 across the masked SSN. $j counts 5, 3, 6... and puts each value from the masked SSN in the correct place in the original SSN.
Looks like you've got good answer, but still there is an alternative. Linear Congruential Generator (LCG) could provide 1-to-1 mapping and it is known to be a reversible using Euclid's algorithm. For 24bit
Xi = [(A * Xi-1) + C] Mod M
where M = 2^24 = 16,777,216
A = 16,598,013
C = 12,820,163
For LCG reversability take a look at Reversible pseudo-random sequence generator
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 :)
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).