pack('H*', dechex(12345678900)) /* on 32bit */
!= pack('H*', dechex(12345678900)) /* on 64bit */
why ?
I don't know how to fix it, but I think I know why this is happening. No bug here - straigt out from the manual http://php.net/manual/en/function.dechex.php
The largest number that can be converted is 4294967295 in decimal resulting to "ffffffff"
I do not know what exactly is happening "inside" php, but you probably are causing 32 bit unsigned integer to overflow (12,345,678,900 > 4,294,967,295). Since on 64 bit this limit should be 18,446,744,073,709,551,615, dechex is returning "correct" values (32 vs 64 bit diffirence doesn't seem to be documented and I might be wrong since I don't have 64 bit system for testing).
//Edit:
As a last resort you could use GMP extesion to make your own hecdex function for 32 bit system, but that is going to produce lots and lots of overhead. Probably going to be one of the slowest implementations known to the modern programming.
//Edit2:
Wrote a function using BCMath, I'm on a Windows at the moment and was struggling finding correct dll for GMP.
function dechex32($i) {
//Cast string
$i = (string)$i;
//Initialize result string
$r = NULL;
//Map hex values 0-9, a-f to array keys
$hex = array_merge(range(0, 9), range('a', 'f'));
//While input is lagrer than 0
while(bccomp($i, '0') > 0) {
//Modulo 16 and append hex char to result
$r.= $hex[$mod = bcmod($i, '16')];
//i = (i - mod) / 16
$i = bcdiv(bcsub($i, $mod), '16');
}
//Reverse result and return
return strrev($r);
}
var_dump(dechex32(12345678900));
/*string(9) "2dfdc1c34"*/
Didn't test thoroughly but seems to work. Use as a last resort - rough benchmarking with 100,000 iterations did show, that it's ~40 times slower than native implemetation.
Related
What is the minimum value allowed for mt_rand()? Is it the same value for 32 bit and 64 bit machines? How could I generate a 32 bit integer using mt_rand() (note that it doesn't need to be highly random)?
BACKGROUND WHY I AM ASKING: I have a 64 bit development physical server and a 32 bit production VPS. Just realized the production server was not generating PKs spanning the full range. To figure out what is going on, I ran the following script. The 64 bit machine never (or at least I've never witnessed) matches, but the 32 bit matches about 50% of the time.
<?php
date_default_timezone_set('America/Los_Angeles');
ini_set('display_errors', 1);
error_reporting(E_ALL);
$count=0;
for ($i = 0; $i <= 10000; $i++) {
$rand=2147483648+mt_rand(-2147483647,2147483647); //Spans 1 to 4294967295 where 0 is reserved
if($rand==2147483649){$count++;}
}
echo('mt_getrandmax()='.mt_getrandmax().' count='.$count);
output
mt_getrandmax()=2147483647 count=5034
TL;DR: To get a random integer in the full range of possible integers, use:
function random_integer() {
$min = defined('PHP_INT_MIN') ? PHP_INT_MIN : (-PHP_INT_MAX-1);
return mt_rand($min, -1) + mt_rand(0, PHP_INT_MAX);
}
For PHP 7, you can use random_int().
Under the hood (1, 2), PHP is doing this:
$number = random_number_between_0_and_0x7FFFFFFF_using_Mersenne_Twister;
$number = $min + (($max - $min + 1.0) * ($number / (0x7FFFFFFF + 1.0)));
Notice $max - $min. When max is set to the top end and min is anything negative, an overflow occurs. Therefore, the maximum range is PHP_INT_MAX. If your maximum value is PHP_INT_MAX, then your minimum is necessarily 0.
Now for the back story. PHP implements the 32-bit Mersenne Twister algorithm. This gives us random integers between [0, and 2^31-1). If you ask for any other range, PHP scales that number using a simple binning function. That binning function includes a subtraction that can lead to overflow, and this problem.
Thus if you want to get a range larger than could be represented by an integer in PHP, you have to add intervals together, like so:
mt_rand(PHP_INT_MIN, -1) + mt_rand(0, PHP_INT_MAX);
Note that PHP_INT_MIN is available since PHP 7, so you'll need to calculate a suitable minimum for your environment before then.
As an aside, notice that 2^31-1 is what getrandmax() returns. People mistakenly believe that on a 64-bit machine getrandmax() will return 2^63-1. That's not true. getrandmax() returns the maximum integer the algorithm will return, which is always 2^31-1.
You can generate a 32 bit integer like this:
$rand = unpack("l", openssl_random_pseudo_bytes(4));
This is a problem that is noted in the PHP docs over here
This works fine on 64 bit Linux:
<?php
printf ("%08x\n", mt_rand (0, 0xFFFFFFFF));
?>
but on our 32 bit Linux development server, it's always yielding 00000000.
On that same machine, this:
<?php
printf ("%08x\n", mt_rand (0, 0xFFFFFFF0));
?>
seems to always yield either 00000000 or a number in the range fffffff2 to ffffffff. This:
<?php
printf ("%08x\n", mt_rand (0, 0xFFFFFF00));
?>
gives numbers where the last two digits vary, and so on through at least 0xF0000000.
However, this:
<?php
printf ("%08x\n", mt_rand (0, 0x7FFFFFFF));
?>
works fine
A bug report is added here.
There has been no word whether PHP is fixing this yet.
In the meantime you can use the mt_rand function between the max_rand and you should be fine
Example Usage
$rand=mt_rand(1,2147483647)+mt_rand(0,2147483647);
I'm trying to print a 40 bit number in php. But when using a Windows machine, it only allows for 32 bit integers which is causing my code echo the wrong result. Example of the code below:
function decoded_microchip_id($coded_string) {
$manufacturer = substr($coded_string, 0, 3);
$manufacturer = hexdec($manufacturer);
$manufacturer = $manufacturer / 4;
$device_id = substr($coded_string, 2, 11);
$device_id = hexdec($device_id);
$device_id = $device_id & 0x3fffffffff;
echo $manufacturer.'.'.$device_id;
};
decoded_microchip_id('f58e29a43c67');
The correct output for the code above is: 982.60828171367
But in my 32 bit Windows environment I'm getting: 982.698629223
When testing the code in various PHP sandbox's the output varies as well.
Here is an example of a sandbox environment giving me the correct output:
http://sandbox.onlinephpfunctions.com/code/83cc28fd5314557e7a9d86d8061e1c8dfae4d1b7
And here is an example of an environment that produces the wrong output:
http://codepad.org/m2ygOgC6
Does anybody know a way around this issue or a solution.
Thanks.
The PHP function hexdec() works fine on 32-bit too. The documentations says it produces float numbers when the result doesn't fit in 32 bits.
It's interesting that 0x3fffffffff is also stored as a float number.
The culprit is the bitwise operator (&) that seems to produce an integer number. Converting its result to float doesn't help, the damage is already done.
This behaviour is documented:
both operands will be converted to integers and the result will be an integer.
A potential solution
You can try to split the input string in pieces that fit in 32 bits and use bitwise operators on them then use addition and multiplication to generate the final value (addition and multiplication convert the result to float if it cannot fit in 32 bits).
A concrete solution
A simpler solution for this situation is to convert the hex string to the binary representation of the number it encodes, extract the bits you need for each component then convert the values to decimal. No bit operations are involved in this case (except for the conversions but those seem to work correctly).
function decoded_microchip_id($coded_string)
{
// Convert to binary
$bin = base_convert($coded_string, 16, 2);
// Split to 10/38 bits
$manufacturer = substr($bin, 0, 10);
$device_id = substr($bin, 10, 38);
// Convert to decimal
$manufacturer = bindec($manufacturer);
$device_id = bindec($device_id);
// Put pieces back
return $manufacturer.'.'.$device_id;
}
Here is my code so far:
function base36($value, $return_size)
{
$base36 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$buffer = str_pad("", $return_size);
$offset = $return_size;
do {
$buffer[--$offset] = $base36[$value % 36];
} while ($value /= 36);
return $buffer;
}
$value: 64bit integer
$return_size: the expected size in bytes the function should return
It doesn't work correctly, because the $value is 64bit integer and because PHP forces double divisions. PHP seems pretty limited when it's about 64bit integers. How to make the above code work like the exact C version would?
Native:
base_convert is a function which happens to do exactly what you want to do.
string base_convert ( string $number , int $frombase , int $tobase )
Its input structure is limited between base 2 and base 36 so it covers what you need. It is most likely (like many other PHP functions) just a light wrapper over the C library originals.
GMP:
gmp_strval is another function which happens to do exactly what you want to do -- it also has better precision (because GMP is a multiprecision arithmetic library).
GMP values need to be initialized from strings using gmp_init and the resulting value (is a resource) is used in all subsequent GMP arithmetic function calls.
It has a higher number of available bases (from 2 to 62) but it is a bit less fun to work with because of the requirement to init the values and use them as resources.
The less fun part isn't true if you're running PHP 5.6 because GMP overloads the arithmetic operators in that version allowing GMP objects (resources?) to be added substracted etc. by using the operators.
Here's a simple GMP conversion function example that doesn't require that you initialize values with gmp_init:
function gmp_convert($num, $base_a, $base_b)
{
return gmp_strval ( gmp_init($num, $base_a), $base_b );
}
I have to calculate a % b for two very large numbers.
I can not use the default modulo operator, because a and b are larger then PHP_INT_MAX, so I have to handle them as "strings".
I know that there exists special math libraries like BC or GMP but I can't use them, because my app probably will hosted on a shared host, where these are not enabled.
I have to write a function in php that will do the job. The function will take two strings (the two number) as parameters and have to return a % b, but I don't know how to start?
How to solve this problem?
Since PHP 4.0.4, libbcmath is bundled with PHP. You don't need any external libraries for this extension. These functions are only available if PHP was configured with --enable-bcmath .
The Windows version of PHP has built-in support for this extension. You do not need to load any additional extensions in order to use these functions. You should be able to enable these functions yourself, without any action on the part of the hosting company.
I though of this solution:
$n represents a huge number, $m the (not so huge) modulus.
function getModulus($n, $m)
{
$a = str_split($n);
$r = 0;
foreach($a as $v)
{
$r = ((($r * 10) + intval($v)) % $m);
}
return $r;
}
Hope it helps someone,
Depending on your processor, if using 64 bit machine 2^63-1 and if 32 bit machine 2^31-1 should give you the length of your decimal your machine can compute. above that you will get wrong values.
You can do the same by splitting your number into chunks.
Example:
my number is 18 decimal long thus, split into chunks of 9/7/2 = 18.
calculate the mod of the first chunk.
Append the mod of the first one to the front of the second chunk.
Example: result of the first mod = 23, thus 23XXXXXXX. find the mod of the resulting 23XXXXXXX. add the mod to the last chunk. Example: mod = 15 then 15XX.
$string = '123456789123456789'; // 18 decimal long
$chunk[0] = '123456789'; // 9 decimal long
$chunk[1] = '1234567'; // 7 decimal long
$chunk[2] = '89'; // 2 decimal long
$modulus = null;
foreach($chunk as $value){
$modulus = (int)($modulus.$value) % 45;
}
The result $modulus above should be same as
$modulus = $tring % 45
Better late than even.
Hope this will help. anyone with similar approach?
You can use fmod for values larger than MAX_INT
Read more about it here
http://php.net/manual/en/function.fmod.php
I have a simple function that I'm using but for some reason the number doesn't calculate correctly as it would in a calculator. I think it has something to do with the numbers being too large, or something to do with 64 bit. Is there any way I can convert them so that they would work correctly?
$sSteamComID = 76561197990369545;
$steamBase = 76561197960265728;
function convertToSteamID($sSteamComID) {
$sServer = bcmod($sSteamComID, '2') == '0' ? '0' : '1';
$sCommID = bcsub($sSteamComID, $sServer);
$sCommID = bcsub($sCommID, $steamBase);
$sAuth = bcdiv($sCommID, '2');
echo "$sServer:$sAuth";
}
convertToSteamID($sSteamComID);
This function outputs 0:15051912 on a server when it should be printing 1:15051908
The missing global $steamBase was the problem, as already mentioned in a comment. (Tip: turn on E_NOTICE during development.) However, I'd like to address your question:
I think it has something to do with
the numbers being too large, or
something to do with 64 bit. Is there
any way I can convert them so that
they would work correctly?
PHP integers are signed and platform-dependent. Using 64-bit numbers will not work if you are on a 32-bit host.
So your concern is valid. But even on a 64-bit system:
$x = 9223372036854775808; // highest bit (64th) set
var_dump($x);
--> float(9.2233720368548E+18)
Note that PHP's BC Math routines operate on strings, not integers. Thus, you should be storing your big numbers as strings.
This will work around the potential problem of integers being converted to floats, which will happen even on your 64-bit environment if you are using large, unsigned integers.