I've got this problem on Windows 10 with both php 7 and 7.1 and also on raspbian with PHP 7.0.33
When I try to cast a large double (a miliseconds timestamp) to int I get a totally wrong result. Example:
$a = 1512298800000.0;
echo intval($a);
The output is: 470311808
Any suggestion on how to troubleshoot this?
Based on intval() Manual, it cleary states:
Return Values
The integer value of var on success, or 0 on failure. Empty arrays return 0, non-empty arrays return 1.
The maximum value depends on the system. 32 bit systems have a maximum signed integer range of -2147483648 to 2147483647. So for example on such a system, intval('1000000000000') will return 2147483647. The maximum signed integer value for 64 bit systems is 9223372036854775807.
Strings will most likely return 0 although this depends on the leftmost characters of the string. The common rules of integer casting apply.
And
Notes
Note:The base parameter has no effect unless the var parameter is a string.
So basically it seems you are using 32-bit system and value got overflowed from the range of integer.
Related
This is the example code I'm running.
<?php
//$maxval = (2**64)-1;
$maxval = (2**64);
$maxval = $maxval-10000;
echo number_format($maxval,0,".","") . "\n";
echo "18446744073709551615\n";
?>
The code if for checking a number that will come as a string and I need to make sure it is an unsigned 64 bit integer. So once I check that the string is composed ONLY of numbers I need to make sure that the range is correct.
But I'm getting some weird behaviour.
This line (2**64)-1; printed 18446744073709551616. It seemed it ignored the -1. So I started changing -1 to -10, -100, -1000 and they all printed the same value. It only changed when I changed it to -10000 and this is what it printed:
18446744073709541376
18446744073709551615
But to my understanding the first line should be
18446744073709541615
So what am I doing wrong?
Integer in PHP is a signed integer. There are constants PHP_INT_MIN and PHP_INT_MAX for the smallest and largest value.
//64 Bit PHP Version !
var_dump(PHP_INT_MAX); //int(9223372036854775807) = 2**63-1
If the values are greater than the integer range, PHP automatically converts to float. Float, however, is far less precise than 63 bits. This is where the inaccuracies come from.
The bcmath functions can be used to calculate with almost any precision. This code is for checking a number that will come as a string and is an unsigned 64 bit integer.
$val = "18446744073709551614";
$maxUnsign64Bit ="18446744073709551615";
if(ctype_digit($val) AND bccomp($val,$maxUnsign64Bit) !== 1){
echo 'is a 64 Bit number';
};
PHP documentation here states that:
If PHP encounters a number beyond the bounds of the integer type, it will be interpreted as a float instead. Also, an operation which results in a number beyond the bounds of the integer type will return a float instead.
But what about an operation which results in a number less than PHP_INT_MAX ?
See this code snippet as an example:
$max_int = 2**31-1 ; // 2147483647
var_dump(PHP_INT_MAX === $max_int); // false
As you can see, even when an operation results in a valid int value PHP seems to cast the result into float
var_dump(PHP_INT_MAX === (int) $max_int) // true
My questions:
Does PHP interpreter cast the result into float before making any calculations?
Shouldn't PHP calculate the result and then sets the type accordingly? (Makes sense right?)
Edit:
PHP version: 7.2.1 32-bit
OS: Windows: 10 x64
I'm using XAMPP
When calculating $max_int = 2**31-1 the engine does this in steps:
$tmp = 2**31;
$max_int = $tmp-1
Here $tmp is bigger than maximum integer value and converted to a floatng point number. In consequence there is an float subtraction, resulting in a float. Since it had been float it has to stay float.
Ignoring the special libraries that allow you to work with very big numbers, what's the largest int value you can store in PHP?
From the PHP manual:
The size of an integer is
platform-dependent, although a maximum
value of about two billion is the
usual value (that's 32 bits signed).
PHP does not support unsigned
integers. Integer size can be
determined using the constant
PHP_INT_SIZE, and maximum value using
the constant PHP_INT_MAX since PHP
4.4.0 and PHP 5.0.5.
64-bit platforms usually have a maximum value of about 9E18, except on Windows prior to PHP 7, where it was always 32 bit.
32-bit builds of PHP:
Integers can be from -2,147,483,648 to 2,147,483,647 (~ ± 2 billion)
64-bit builds of PHP:
Integers can be from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 (~ ± 9 quintillion)
Numbers are inclusive.
Note: some 64-bit builds once used 32-bit integers, particularly older Windows builds of PHP
Values outside of these ranges are represented by floating point values, as are non-integer values within these ranges. The interpreter will automatically determine when this switch to floating point needs to happen based on whether the result value of a calculation can't be represented as an integer.
PHP has no support for "unsigned" integers as such, limiting the maximum value of all integers to the range of a "signed" integer.
The size of PHP ints is platform dependent:
The size of an integer is
platform-dependent, although a maximum
value of about two billion is the
usual value (that's 32 bits signed).
PHP does not support unsigned
integers. Integer size can be
determined using the constant
PHP_INT_SIZE, and maximum value using
the constant PHP_INT_MAX since PHP
4.4.0 and PHP 5.0.5.
PHP 6 adds "longs" (64 bit ints).
(a little bit late, but could be useful)
Only trust PHP_INT_MAX and PHP_INT_SIZE, this value vary on your arch (32/64 bits) and your OS...
Any other "guess" or "hint" can be false.
Ah I found it: 232 - 1 (2147483647)
http://au2.php.net/int
Integer overflow
If PHP encounters a number beyond the bounds of the integer type, it will be interpreted as a float instead. Also, an operation which results in a number beyond the bounds of the integer type will return a float instead.
<?php
$large_number = 2147483647;
var_dump($large_number);
// output: int(2147483647)
$large_number = 2147483648;
var_dump($large_number);
// output: float(2147483648)
It depends on your OS, but 2147483647 is the usual value, according to the manual.
It subjects to architecture of the server on which PHP runs. For 64-bit,
print PHP_INT_MIN . ", ” . PHP_INT_MAX; yields -9223372036854775808, 9223372036854775807
Although PHP_INT_* constants exist for a very long time, the same MIN / MAX values could be found programmatically by left shifting until reaching the negative number:
$x = 1;
while ($x > 0 && $x <<= 1);
echo "MIN: ", $x;
echo PHP_EOL;
echo "MAX: ", ~$x;
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
I am trying to get a string number to an integer but it's not working as expected here is the code with the problem:
$usage['msisdn'] = "46720000000";
$usage['msisdn'] = (int)$usage['msisdn'];
echo $usage['msisdn'];
It echoes 2147483647 as integer but I want to get 46720000000 as integer.
What's wrong?
By the way I'm parsing the data using json_encode();
UPDATE: Nevermind I've got it to work with intval()
That's because the maximum value of int32 is 2,147,483,647. Your phone number exceeds this value.
You can find the maximum value of int on your server using:
echo PHP_INT_MAX;
I think that storing a phone number as integer is a bad practice and it may affect you later. Why? Because the phone number may start with:
IDD: 00 or +
NDD: 0
Also, you may want to format the phone number at some point, storing it as string making this part much easier.
Christmas bonus :) libphonenumber-for-php
Your code isn't working because the conversion string to int as a maximum value based on your system as quoted in the documentation :
The maximum value depends on the system. 32 bit systems have a maximum signed integer range of -2147483648 to 2147483647. So for example on such a system, intval('1000000000000') will return 2147483647. The maximum signed integer value for 64 bit systems is 9223372036854775807.
Source : http://php.net/manual/en/function.intval.php
Since you cannot modify the max_int value easily, you could try to use a conversion to float instead.
$usage['msisdn'] = floatval($usage['msisdn']);