PHP does not support unsigned ints. Is there a way to convert a string representation of an unsigned integer into a signed integer with overflow?
Example:
On a 32 bit system, PHP can store int values <= 2147483647. I want a way to convert the string "2147483648" to integer, causing it to overflow to -2147483648 instead of being reduced to 2147483647.
Why do I want to do this?
I store IPv4 addresses in a database as unsigned int (32 bits). I want to do binary operations on the addresses in PHP to check for subnets. This needs to be done on every request, so it needs to be quick. Therefore it seems better to store the IP address as an unsigned int rather than storing a string which will have to be converted back and forth.
here is the workaround.
<?php
$unsignedString = "3000000000";
echo "unsigned string: ".$unsignedString."<br />";
$signedInt = intval(doubleval($unsignedString));
echo "signed int: ".$signedInt."<br />";
?>
The fastest and easiest way to do this is to get your RDBMS to do it somehow.
You can find out what the size of an integer is in PHP by checking the value of the predefined constant PHP_INT_SIZE. This will be 4 if running on a 32-bit system, 8 if running on a 64-bit system.
I suggest that you populate a variable with for example
$smallIntegers = intval(PHP_INT_SIZE == 4);
and then in your query use something like this:
SELECT
...,
CASE
WHEN :smallIntegers: = 1 AND IPAddress > 2147483647 THEN IPAddress - 4294967296
ELSE IPAddress
END AS IPAddress,
...
...
Could you store the IP address in an integer array, one segment of the IP to each part of the array so the max value would end up as 255 stored in anyone one integer:
$ipAddress = array(127, 0, 0, 1);
Related
My code:
$ip = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'][0]); // Gives an IP address string. Example: "176.10.99.207"
$ipL = ip2long($ip); // An integer, which for this example would be 2953470927.
if( $stmt_insert_ip = $ip_link->prepare("INSERT INTO dataTable(ip,datetime) VALUES(?,?)") ){
$stmt_insert_ip->bind_param('is',$ipL,date("Y-m-d H:i:s"));
}
But when I check my MySQL table, the newly-inserted value under the ip column is 2147483647, which is "127.255.255.255".
When I run echo($ipL);, however, I get 2953470927.
What the H-E-Double hockey sticks is going on here?
I assume you have used INT as datatype in your database. To make this story short just change datatype of ip column to BIGINT and problem will be solved.
INT range (-2147483648, 2147483647)
BIGINT range (-9223372036854775808, 9223372036854775807)
2953470927 goes bit over the range of signed INT.
Refference: MySQL: Integer types
Unfortunately depending on your system (32-bit vs 64-bit) PHP returns different values.
on a 32-bit system it uses signed integers, and on a 64-bit machine it uses unsigned integers, so you'll need to store the data accordingly.
Based on what you returned it seems like you're using a 64-bit machine, so change your mysql column to an unsigned integer, or a signed or unsigned bigint
All phone numbers I am trying to enter into my database are being inserted as 2147483647.
The database field is an integer(11).
Before the phone number is inserted, it goes through the following code in order to remove all spaces, dashes, and brackets:
if (!empty($hphone)) $phone = $hphone;
else if (!empty($HomePhone)) $phone = $HomePhone;
else if (!empty($Phone1)) $phone = $Phone1;
$phone = preg_replace("/[^0-9]/", "", $phone);
Why is it inserting the phone number as 2147483647 every time, no matter what the phone number is?
If you can, convert the phone number to a VARCHAR field, don't store it as a signed (or unsigned) numeric value (int, bigint, double, etc...).
In this case, the limit for signed INT in MySQL of 2147483647 is what is causing your issue. Anything larger inserted into this field will be capped at that maximum value.
For example the phone number 555-555-5555 if bigger than that limit (5555555555 >2147483647), as such storing it would result in the max value 2147483647.
I also recommend not storing it as a BIG INT or any other numeric type. How will you handle extension or special encoded characters like:
+02 112020 10020
1-333-232-9393 x203
BTW: don't know if the first is real non-US number format, but you get the idea
Also, phone numbers with relevant leading 0's would be have some of it lost no mater how large the number:
021-392-9293
Would be the number 213929293
if you want to store it as a number use bigint because int has it's max value equal to 2147483647.
So whatever number you try to store that is higher than 2147483647 will be stored as 2147483647.
Here is some reference for mysql types:
http://dev.mysql.com/doc/refman/5.0/en/integer-types.html
I am also facing this type of problem and I have solved that problem after
Updating datatype From 'INT' TO 'BIGINT'.
#php #mysql #database #sql
To put my question into perspective:
I have a PHP app, which stores IP's of users in a MySQL table.
The column type is VARBINARY(16), and the app uses PHP's inet_pton to form a binary string.
That is the string has 4 bytes for a typical IP4 address.
How to retrieve these IPs from the table, displaying them in a human readable form?
My current solution is:
select INET_NTOA(CONV(HEX(ip),16,10)) from operation_ip;
Is there a more direct way to do that?
In particular is CONV(HEX(x),16,10) the easiest way to change 4 bytes into an integer (actually I believe it is not even an integer, but a string which looks like integer).
(I use VARBINARY(16), as PHP's inet_pton can return 16-bytes for IPv6 addresses. AFAIK MySQL's INET_NTOA does not support IPv6, but at the moment this is not my biggest concern).
MySQL has nice built-in functions to handle this case:
INSERT INTO t(ip) VALUE (INET_ATON('1.2.3.4')); -- stores integer 16909060
SELECT INET_NTOA(ip) FROM t; -- returns string '1.2.3.4'
Or the PHP way:
$sql = 'INSERT INTO t(ip) VALUE (' . ip2long('1.2.3.4') . '))';
// ip2long('1.2.3.4') = 16909060
// reverse conversion: long2ip(16909060) returns '1.2.3.4'
I'll try and keep this simple. I'm using a BIGINT data type on a MySQL database table. I have a function generating a unique random number that may span anywhere from 12 digits to 13 digest long.
When I insert into the database with a digit that is 12 digits in length, it enters it just fine,
but when I use a value that is 13 digits or longer, it seems like it rounds up or something. here is the
php
$defaultText = 'some string'; //just some string
$web_id = 12;
$element_id = 23112182735363; //case 1 (doesn't work)
//$element_id = 2311218333205; //case 2, does work ()
mysql_query("INSERT INTO tableName (web_id, element_id, content)
VALUES ($web_id, $element_id, '".mysql_real_escape_string($defaultText)."')");
results:
in case one, it inserts a slightly different number, usually rounds up for some reason.
in case two, it inserts the number just fine! Maybe someone can help shed some light on this mystery! Thanks again!
the big int datatype:
bigint(200)
Numbers lose their precision from PHP_INT_MAX onwards. See also: http://www.php.net/manual/en/reserved.constants.php#constant.php-int-max
After that they are turned into floats which have limited precision and causes weird rounding issues. The only way to support BIGINT in PHP is by using strings.
I assumed you were talking about a 32-bit server.
But in my server, PHP seemed not lose the precision.
echo(PHP_INT_MAX . "<br/>");
$big_int = -6174803190685607000;
echo($big_int . '<br/>');
output
9223372036854775807<br/>-6174803190685607000<br/>
Sadly I still got the precision losing. I guessed it might because i used 'i' in prepare statement of mysqli, but I could not prove it.
It appears, that in 32bit OS ip2long returns signed int, and in 64bit OS unsigned int is returned.
My application is working on 10 servers, and some are 32bit and some are 64bit, so I need all them to work same way.
In PHP documentation there is a trick to make that result always unsigned, but since I got my database already full of data, I want to have it signed.
So how to change an unsigned int into a signed one in PHP?
PHP does not support unsigned integers as a type, but what you can do is simply turn the result of ip2long into an unsigned int string by having sprintf interpret the value as unsigned with %u:
$ip="128.1.2.3";
$signed=ip2long($ip); // -2147417597 in this example
$unsigned=sprintf("%u", $signed); // 2147549699 in this example
Edit, since you really wanted it to be signed even on 64 bit systems - here's how you'd convert the 64 bit +ve value to a 32 bit signed equivalent:
$ip = ip2long($ip);
if (PHP_INT_SIZE == 8)
{
if ($ip>0x7FFFFFFF)
{
$ip-=0x100000000;
}
}
Fwiw, if you're using MySQL it's usually a lot easier and cleaner if you just pass in the IPs as strings to the database, and let MySQL do the conversion using INET_ATON() (when INSERTing/UPDAT(E)'ing) and INET_NTOA() (when SELECTing). MySQL does not have any of the problems described here.
Examples:
SELECT INET_NTOA(ip_column) FROM t;
INSERT INTO t (ip_column) VALUES (INET_ATON('10.0.0.1'));
The queries are also much more readable.
Note that you can not mix INET_NTOA()/INET_ATON() in MySQL with ip2long()/long2ip() in PHP, since MySQL uses an INT UNSIGNED datatype, while PHP uses a signed integer. Mixing signed and unsigned integers will seriously mess up your data!
interpreting an integer value as signed int on 32 and 64 bit systems:
function signedint32($value) {
$i = (int)$value;
if (PHP_INT_SIZE > 4) // e.g. php 64bit
if($i & 0x80000000) // is negative
return $i - 0x100000000;
return $i;
}
-Misunderstood problem, see Paul Dixon's answer above.
64bit unsigned integers are not technically supported in PHP5. It will use the native type. To convert to a 32bit signed int from a 64bit signed int without losing the high bit, you could mask and then type cast the output:
$ip_int = ip2long($ip);
if (PHP_INT_SIZE == 8) // 64bit native
{
$temp_int = (int)(0x7FFFFFFF & $ip_int);
$temp_int |= (int)(0x80000000 & ($ip_int >> 32));
$ip_int = $temp_int;
}
On a 64 bit system, printing this value ($ip_int) will display an 'unsigned' integer since we've removed the high bit. However this should allow you to take the output and store it how you wish.
public function unsigned2signed($num) { // converts unsigned integer to signed
$res = pack('i',$num); // change to 'l' to handle longs
$res = unpack('i',$res)[1];
return $res;
}
unsigned-int is a php library to convert signed integers to unsigned. (Disclaimer: I'm the author)
use Oct8pus\Unsigned\UInt32;
require_once './vendor/autoload.php';
$uint32 = new UInt32(-2147483648);
echo $uint32;
-2147483648 > 0x80000000 (2147483648)
The repository is here https://github.com/8ctopus/unsigned-int