PHP: unpacking in_addr to a string representation of an int - php

The inet_pton function returns an IP address in its packed in_addr representation, i.e. ::1 becomes ``. How can I convert this 16-byte value to an integer? (Well, the string representation of an integer.)

Matty, seems there should be a capital "A" used in unpack() functions, otherwise you will lose last 4 bits if they are nulls:
$unpacked = unpack('a16', $in_addr);
2001:0db8:11a3:09d7:1f34:8a2e:07a0:7600
is converted to string(120)
$unpacked = unpack('A16', $in_addr);
2001:0db8:11a3:09d7:1f34:8a2e:07a0:7600
is converted to string(128)
Also this solution can be used for same conversion results.

The following code does it:
$in_addr = inet_pton('21DA:00D3:0000:2F3B:02AA:00FF:FE28:9C5A');
$unpacked = unpack('a16', $in_addr);
$unpacked = str_split($unpacked[1]);
$binary = '';
foreach ($unpacked as $char) {
$binary .= str_pad(decbin(ord($char)), 8, '0', STR_PAD_LEFT);
}
$gmp = gmp_init($binary, 2);
$str = gmp_strval($gmp);
You can also reverse it with the following code:
$gmp = gmp_init($str);
$binary = gmp_strval($gmp, 2);
$binary = str_pad($binary, 128, '0', STR_PAD_LEFT);
$binary_arr = str_split($binary, 8);
$packed = '';
foreach ($binary_arr as $char) {
$packed .= chr(bindec($char));
}
$packed = pack('a16', $packed);
echo inet_ntop($packed);

You can't represent a 16-byte (128-bit) integer precisely in a PHP number -- they're typically stored as doubles, which lose precision before then. Here's how you can unpack one into four 32-bit integers without using GMP, though:
$in_addr = inet_pton('2001:0db8:85a3:0000:0000:8a2e:0370:7334');
list($junk, $a, $b, $c, $d) = unpack("N4", $in_addr);
printf("%08x %08x %08x %08x", $a, $b, $c, $d);
Also note the inet_ntop function, which will turn the output of inet_pton back into a human-readable format.

Related

Remove trailing zeros from decimal string

I have these numbers:
0.00000100
0.00100000
0.01000000
0.00001000
and I want to remove unnecessary zero in decimal by using this :
$decimal_cutter = (float) $decimal_cutter;
echo sprintf('%f', $decimal_cutter) . '<br>';
and it works for some numbers, some others produce this :
1.0E-6
0.001
0.01
1.0E-5
I want to use decimal, instead of scientific format.
Please note, I tried to use number_format() also, but keep in mind that by setting number of decimal points can cut the rest of numbers. I just want to remove the 0 after 1.
If you are simply worried about not displaying those characters, why not simply trim them?
You can simply call rtrim($yourNumber, '0')
E.g.:
$a = [];
$a[] = "0.00000100";
$a[] = "0.00100000";
$a[] = "0.01000000";
$a[] = "0.00001000";
foreach ($a as $b) {
echo rtrim($b, '0'), "\n";
}
Which outputs:
0.000001
0.001
0.01
0.00001
Logically this approach requires that you any of the decimal digits is different from 0, otherwise a number such as 2.0000 would be displayed as 2., which might not be ideal.
You could build a more general purpose solution using preg_replace
$a = [];
$a[] = "0.00000100";
$a[] = "0.00100000";
$a[] = "0.01000000";
$a[] = "0.00001000";
$a[] = "2.00000000";
$a[] = "2.01011000";
$a[] = "0.00123000";
foreach ($a as $b) {
echo preg_replace('|(?<!\.)0+$|', '', $b), "\n";
}
This looks for all trailing zeroes not preceded by a period, and would output:
0.000001
0.001
0.01
0.00001
2.0
2.01011
0.00123
Since you mention your data is always "1", here's a simple way:
$s = '0.00000100';
echo strstr($s, '1', true).'1';
Note: make sure you convert your data to string first

convert a string to persian number format

I have a number string in Persian numbers for example
۱۱۲۵۱۲۰۱۲۴
which is 1125120124 in English numerical. I want to convert this string to number format separated by commas after every thousand for example
۱,۱۲۵,۱۲۰,۱۲۴
like when I do
number_format(1125120124); // it returns as 1,125,120,124
and
number_format(112512012); // it returns as 112,512,012
So actualy I want similar results as of number_format method. I just started regular expressions. Tried with very basic patterns but still no where near. Tried preg_split to split string and concatenate it again with commas but does not seem to be right approach. I have a function where I pass a number that returns me that number in Persian characters. Sharing that too
function trans($num)
{
$persian_num_array = [
'0'=>'۰',
'1'=>'۱',
'2'=>'۲',
'3'=>'۳',
'4'=>'۴',
'5'=>'۵',
'6'=>'۶',
'7'=>'۷',
'8'=>'۸',
'9'=>'۹',
];
$translated_num = '';
$temp_array=[];
while ($num > 0) {
array_push($temp_array,$num % 10);
$num = intval($num / 10);
}
foreach($temp_array as $val){
$translated_num.= $persian_num_array[array_pop($temp_array)];
}
echo $translated_num;
}
As converting to Persian is a just character replacing, you can format number using built-in number_format() function and then replace numbers without replacing commas. Here is an example:
function trans($num)
{
$persian_num_array = [
'0'=>'۰',
'1'=>'۱',
'2'=>'۲',
'3'=>'۳',
'4'=>'۴',
'5'=>'۵',
'6'=>'۶',
'7'=>'۷',
'8'=>'۸',
'9'=>'۹',
];
$num = (float) $num;
return strtr(number_format($num), $persian_num_array);
}
echo trans(1125120124); // returns ۱,۱۲۵,۱۲۰,۱۲۴
PHP's Intl extension includes a NumberFormatter class that can format your number for a given locale.
Example:
$number = "1125120124";
$formatter = NumberFormatter::create("fa_IR", NumberFormatter::DEFAULT_STYLE);
echo $formatter->format($number);
Output:
۱٬۱۲۵٬۱۲۰٬۱۲۴
To force a comma to be used, it has a setSymbol() method.
Example:
$number = "1125120124";
$formatter = NumberFormatter::create("fa_IR", NumberFormatter::DEFAULT_STYLE);
$formatter->setSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL, ",");
echo $formatter->format($number);
Output:
۱,۱۲۵,۱۲۰,۱۲۴
Or if your input was in Persian, first use a Transliterator on it.
Example:
$input = "۱۱۲۵۱۲۰۱۲۴";
$transliterator = Transliterator::create("fa-fa_Latn/BGN");
$number = $transliterator->transliterate($input);
echo $number, "\n";
$formatter = NumberFormatter::create("fa_IR", NumberFormatter::DEFAULT_STYLE);
$formatter->setSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL, ",");
echo $formatter->format($number);
Output:
1125120124
۱,۱۲۵,۱۲۰,۱۲۴

Sort an array based on another array in php?

according to this answer -> Sort an Array by keys based on another Array?, I use this function to get my another array sorted:
function sortArrayByArray($array,$orderArray) {
$ordered = array();
foreach($orderArray as $key) {
if(array_key_exists($key,$array)) {
$ordered[$key] = $array[$key];
unset($array[$key]);
}
}
return $ordered + $array;
}
at first, I have my code like this and it works fine
$array1 = array("a","b","c");
$array2 = array("2","5","1");
$array3 = array("2","5","1");
rsort($array3); //5,2,1
for($i=0;$i<3;$i++){
$customer1[$array2[$i]] = $array1[$i];
}
$properOrderedArray1 = sortArrayByArray($customer1, $array3);
print_r($properOrderedArray1);
but when I use some logic math like multiply, it gets any errors like it said there is data type float
//multiply
$a = 100000*100000;
$b = 200000*200000;
$c = 300000*300000;
$array1 = array("a","b","c");
$array2 = array($a,$b,$c);
$array3 = array($a,$b,$c);
rsort($array3); //5,2,1
for($i=0;$i<3;$i++){
$customer1[$array2[$i]] = $array1[$i];
}
$properOrderedArray1 = sortArrayByArray($customer1, $array3);
print_r($properOrderedArray1);
var_dump($array2);
THE ERROR: Warning: array_key_exists(): The first argument should be either a string or an integer
so any solution for this problem?
Thanks.
As stated, you will have to convert your floats to strings. I guess you can alter your function to something like this to make it work:
function sortArrayByArray($array,$orderArray) {
$ordered = array();
foreach($orderArray as &$key) {
if (!is_int($key))
$key = number_format($key,0,'.','');
if(array_key_exists($key,$array)) {
$ordered[$key] = $array[$key];
unset($array[$key]);
}
}
return $ordered + $array;
}
The problem about doing it this way is that you lose precision in your float value. In a 64-bit system all these values
$a = 9223372036854775808;
$b = 9223372036854775809;
$c = 9223372036854775810;
will be converted into the same float(9.2233720368548E+18), and converting it to a string will give string(19) "9223372036854775808". As long as your indexes that you use for sorting have significant numbers in the upper range of the number it can work, but it's not a safe way of sorting.
Basically what i written in the comment is true as i just checked in the manual: http://php.net/manual/en/language.types.integer.php
$large_number = 2147483647; //treated as int
$large_number = 2147483648; //treated as float since it is too large for an integer
So the solution to your problem is to limit the indices to the max value of integers.
OR to convert the floats to strings, but i would NOT recommend that, but sadly that seems to be the only way to achieve what you are trying to do.

How to convert Exponentials to Decimals in PHP

I have a string like this:
9.018E-14
Now I want to convert to this to the normal decimal numbers.
MyGeekPal has a nice article on it.
Code:
<?php
$total_time = 2.8848648071289E-5;
echo exp2dec($total_time);
function exp2dec($number) {
preg_match('/(.*)E-(.*)/', str_replace(".", "", $number), $matches);
$num = "0.";
while ($matches[2] > 0) {
$num .= "0";
$matches[2]--;
}
return $num . $matches[1];
}
?>
If your input is a float
If you have $number = 0.00023459 then printing this value in PHP will probably result in this exponential format. It doesn't mean the variable is stored that way; it's just an output artefact.
Use printf to work around this and gain control over your numeric output.
If your input is a string
Why the complexity?
$matches = Array();
if (preg_match('/(.*)E-(.*)/', $number, $matches)) {
$number = $matches[1] * pow(10, -1*$matches[2]);
}
Though you can tighten up the regex a bit:
$matches = Array();
if (preg_match('/(\d+(?:\.\d+)?)E(-?\d+)/i', $number, $matches)) {
$number = (float)$matches[1] * pow(10, (int)$matches[2]);
}
Live demo
EDIT: Here is some PHP magic:
$stringval = "12e-3";
$numericval = 0 + $stringval;
From the PHP docs:
If the string does not contain any of the characters '.', 'e', or 'E' and the numeric value fits into integer type limits (as defined by PHP_INT_MAX), the string will be evaluated as an integer. In all other cases it will be evaluated as a float.
If you need a more flexible format (e.g. extract four numbers from the same string), use sscanf like this:
$stringval = "12e-3";
$numericval = sscanf($stringval, "%f")[0];
echo $numericval;

PHP Compress array of bits into shortest string possible

I have an array that contains values of 1 or 0 representing true or false values. e.g.
array(1,0,0,1,0,1,1,1,1);
I want to compress/encode this array into the shortest string possible so that it can be stored within a space constrained place such as a cookie. It also need to be able to be decoded again later. How do I go about this?
ps. I am working in PHP
Here is my proposal:
$a = array(1,0,0,1,0,1,1,1,1,1,0,0,1,0,1,1,1,1,1,0,0,1,0,1,1,1,1);
$compressed = base64_encode(implode('', array_map(function($i) {
return chr(bindec(implode('', $i)));
}, array_chunk($a, 8))));
var_dump($compressed); // string(8) "l8vlBw=="
So you get each 8 characters (which in fact is a binary 0..255), convert them to an integer, represent as an ASCII character, implode it to a string and convert to base64 to be able to save it as a string.
UPD:
the opposite is pretty straightforward:
$original = str_split(implode('', array_map(function($i) {
return decbin(ord($i));
}, str_split(base64_decode($compressed)))));
How exactly I wrote it (just in case anyone interesting how to write such unreadable and barely maintainable code):
I've written the $original = $compressed; and started reversing the right part of this expression step by step:
Decoded from base64 to a binary string
Split it to an array
Converted every character to its ASCII code
Converted decimal ASCII code to a binary
Joined all the binary numbers into a single one
Split the long binary string to an array
Dont use serialize. Just make a string of it:
<?php
$string = implode( '', $array );
?>
You are left with an string like this:
100101111
If you want to have an array again, just access it like an array:
$string = '100101111';
echo $string[1]; // returns "0"
?>
Of course you could also make it a decimal and just store the number. That's even shorter then the "raw" bits.
<?php
$dec = bindec( $string );
?>
How about pack and unpack
$arr = array(1,1,1,1,0,0,1,1,0,1,0,0,1,1,0,0,1,1,1,1);
$str = implode($arr);
$res = pack("h*", $str);
var_dump($res);
$rev = unpack("h*", $res);
var_dump($rev);
output:
string(10) # Not visible here
array(1) {
[1]=>
string(20) "11110011010011001111"
}
Here is my solution based on zerkms answer, this deals with the loss of leading 0's when converting decimals back into binary.
function compressBitArray(array $bitArray){
$byteChunks = array_chunk($bitArray, 8);
$asciiString = implode('', array_map(function($i) {
return chr(bindec(implode('', $i)));
},$byteChunks));
$encoded = base64_encode($asciiString).'#'.count($bitArray);
return $encoded;
}
//decode
function decompressBitArray($compressedString){
//extract origional length of the string
$parts = explode('#',$compressedString);
$origLength = $parts[1];
$asciiChars = str_split(base64_decode($parts[0]));
$bitStrings = array_map(function($i) {
return decbin(ord($i));
}, $asciiChars);
//pad lost leading 0's
for($i = 0; $i < count($bitStrings); $i++){
if($i == count($bitStrings)-1){
$toPad = strlen($bitStrings[$i]) + ($origLength - strlen(implode('', $bitStrings)));
$bitStrings[$i] = str_pad($bitStrings[$i], $toPad, '0', STR_PAD_LEFT);
}else{
if(strlen($bitStrings[$i]) < 8){
$bitStrings[$i] = str_pad($bitStrings[$i], 8, '0', STR_PAD_LEFT);
}
}
}
$bitArray = str_split(implode('', $bitStrings));
return $bitArray;
}

Categories