Why php thinks large ints are floats (but only sometimes)? - php

Sorry for the bad title, but I dunno how to call this.
echo rand(0,10e20) . "\n"; // bad
echo rand(0,10e19) . "\n"; // bad
echo rand(0,10e18) . "\n"; // bad
echo rand(0,10e17) . "\n"; // OK
echo rand(0,10e16) . "\n";
echo rand(0,10e15) . "\n\n";
var_dump(10e20); // float
var_dump(10e15); // float
Output:
Warning: rand() expects parameter 2 to be integer, float given
in /srv/webroot-sandbox/index.php(73) : eval()'d code on line 1
Warning: rand() expects parameter 2 to be integer, float given
in /srv/webroot-sandbox/index.php(73) : eval()'d code on line 2
Warning: rand() expects parameter 2 to be integer, float given
in /srv/webroot-sandbox/index.php(73) : eval()'d code on line 3
578009006101638016
69608699344098568
7596902768127620
float(1.0E+21)
float(1.0E+16)
Can someone explain what's going on? This is PHP 7, it worked fine in PHP 5 (well, at least I didn't get any warnings).

PHP ints are signed 64bit values (unless you're on a 32bit install), so they go (roughly)
-9,223,372,036,854,775,808 -> +9,223,372,036,854,775,808
In scientific notation, -9.2e18 -> +9.2e18
So your "bad" values are simply integers that are too large to store as integers, and PHP is converting to float to try and preserve as much of the value as is possible.
And since you have 10e18, that's actually 1e19, and outside the max_int range.

Your question can be platform dependent as the integer range of a:
32 bit platform is -2,147,483,648 to 2,147,483,647
64 bit platform is -9,223,372,036,854,775,808 to 9,223,372,036,854,775,808
For me, running a 64 bit system it gives the following result.
var_dump(10e20 > PHP_INT_MAX); // true
var_dump(10e19 > PHP_INT_MAX); // true
var_dump(10e18 > PHP_INT_MAX); // true
var_dump(10e17 > PHP_INT_MAX); // false
var_dump(10e16 > PHP_INT_MAX); // false
var_dump(10e15 > PHP_INT_MAX); // false
This output directly correlates with your results and might explain a fiddle or your webhost to show different results.
The reason it behave differently on PHP 7 is explained here:
Previously, internal functions would silently truncate numbers
produced from float-to-integer coercions when the float was too large
to represent as an integer. Now, an E_WARNING will be emitted and NULL
will be returned.

Related

Numeric problems in PHP (max possible value for a 64 unsigned integer)

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';
};

Minimum integer in PHP 7

In interactive mode on PHP 7 (64 bit Ubuntu),
php > echo true;
1
php > echo false; # no output for false
php > echo PHP_INT_MIN == -9223372036854775808;
1
php > echo is_int(PHP_INT_MIN);
1
php > echo is_int(-9223372036854775808);
Why doesn't the last line output 1?
var_dump() is your friend.
var_dump(
PHP_INT_MIN,
is_int(PHP_INT_MIN),
-9223372036854775808,
is_int(-9223372036854775808)
);
/* Output:
int(-9223372036854775808)
bool(true)
float(-9.2233720368548E+18)
bool(false)
*/
Because is_int :
perates in signed fashion, not unsigned, and is limited to the word size of the environment php is running in.
and so -9223372036854775808 it's smaller than your system word bound
First PHP parses your integer value as float, because of an integer overflow. Then it uses is_int() to determine if what PHP has parsed is an integer. See example #3 for 64-bit systems.
Please note that is_int() does work with unsigned integers as well.
Using 32-bit PHP 7:
echo PHP_INT_MIN; // -2147483648
echo var_dump(is_int(-2147483648)); // bool(false)
echo var_dump(is_int(-2147483647)); // bool(true)
To make sure PHP_INT_MIN is off by one, please try this on your 64-bit PHP 7:
echo var_dump(is_int(-9223372036854775807)); // bool(true)?
If you need more integer precision, you could use the GMP extension.

PHP 7 | chr() + strict_types === error?

I try to convert my working PHP 5.x code into an more modern PHP 7.x code-base, so I added "declare(strict_types=1);" in the first step, but it didn't work as expected.
code: ord(chr(ord("\xE9") / 64) | "\xC0");
demo: https://3v4l.org/680ts
github: https://github.com/voku/portable-utf8/blob/master/src/voku/helper/UTF8.php#L6613
PHP < 7.0 or > 7.0 (without strict_types=1) === 195
PHP > 7.0 (with strict_types=1) === 192
Maybe someone can explain this to me? I think it's because of "chr()" expecting an integer, but we get a float?! But there isn't any warning or something like this...? -> http://php.net/manual/en/migration70.incompatible.php#migration70.incompatible.strings.hex
So, let's break it down.
ord("\xC0");
That's 192. With or without strict types. The breakdown is happening here
chr(ord("\xE9") / 64)
Now ord("\xE9") / 64 works out to 3.640625 and we can see where it breaks down here
var_dump(ord(chr(3.640625)));
This is 0 with strict types, and 3 without. Per the manual for chr
string chr ( int $ascii )
So the result here is a float, not an int. What's happening is that strict types cannot convert the float to an int, so it passes a 0
Per the manual
In strict mode, only a variable of exact type of the type declaration will be accepted, or a TypeError will be thrown. The only exception to this rule is that an integer may be given to a function expecting a float.
We're going the other way. So strict types behaves like all PHP when you pass it a bad argument
var_dump(ord(chr('bob'))); // string 0, in all PHP versions

PHP's Infinit value in Bitwise Operations returns strange values

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

Discrepancy between BASIC INT and PHP int, how to resolve?

I've been re-writing a BASIC program to PHP. I've run across a problem, let me explain:
In the BASIC code I have the following statement with values:
LSLATVER=1590
PITCHVER=50
NRSLATHOR=INT(LSLATVER/PITCHVER-.8)
// output: 30
In PHP the code I have two examples like this:
$length_vertical_slat = 1590;
$vertical_pitch = 50;
echo $number_of_horizontal_slats = (int) ($length_vertical_slat / $vertical_pitch - .8);
// output: 31
echo $number_of_horizontal_slats = (int) ($length_vertical_slat / ($vertical_pitch - .8));
// output: 32
Why is there such a huge difference between the 3 examples? I need the PHP to output a value of 30 in this example but I do not see how to go about it. Please help ty!
The BASIC is using integer division, as well as reducing the final result to an int, so you'll want to mimic this in PHP (PHP converts to float by default, rather than reducing to an int).
This means that at BOTH stages (the division, and the subtraction) you'll want to reduce the value to an int. The PHP docs recommend doing this by casting to an int, like you did in your examples:
$length_vertical_slat = 1590;
$vertical_pitch = 50;
// outputs 30
echo $number_of_horizontal_slats = (int)((int)($length_vertical_slat / $vertical_pitch) - .8);
From the PHP docs:
There is no integer division operator in PHP. 1/2 yields the float
0.5. The value can be casted to an integer to round it downwards, [...]

Categories