Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why two float variables with PHP_INT_MAX values are same unless one of them is added with value greater than 1025

<?php
$x=PHP_INT_MAX;
echo ((float)($x+1026)==(float)($x))?'EQUAL':'Not Equal';

I know floating point arithmetic is not exact and $x and $x+1 are so close together that they are rounded to the same floating point value and it shows the output as EQUAL if you use any number between 1 and 1025 but its only after you use value beyond 1025 it will start giving output as 'Not Equal'. I want to know why? What's the reason behind it? Why only after 1025?

like image 856
Varun Krish Avatar asked Sep 27 '22 03:09

Varun Krish


1 Answers

With float, your assumption $x == $x + 1 is not necessarily true:

$x=2;
echo ((float)($x+1)==(float)($x))?'EQUAL':'Not Equal';

yields "Not Equal".

In the converter linked in the comments (http://www.h-schmidt.net/FloatConverter/IEEE754.html), you can reproduce this. decimal 2.0 yields 0x40000000, decimal 3.0 yields 0x40400000, so they're indeed different when it comes to IEEE754 float representation.

Whereas, e.g., decimal 0.1 cannot be represented as float: 0x3dcccccd, which is 0.10000000149011612.

What's decimal 9223372036854775807? That's 0x5f000000, which is 9.223372E18, which is 9223372180000000000.

What's decimal 9223372036854775808 (PHP_MAX_INT + 1)? That's 0x5f000000, too.

What's decimal 9223372036854776832 (PHP_MAX_INT + 1025)? That's 0x5f000000, too.

What's decimal 9223372036854776833 (PHP_MAX_INT + 1026)? That's 0x5f000000, too.

They're all the same.

Whereas, e.g.: decimal 9223373000000000000 (PHP_MAX_INT + 963145224193)? That's 0x5f000001, which is 9.223373E18, which is 9223373000000000000.

Now, why does:

((float)($x+1025)==(float)($x+1026))?'EQUAL':'Not Equal';

yield "Not Equal"?

You're adding an integer to PHP_MAX_INT.

$x=PHP_INT_MAX;
$y=PHP_INT_MAX-1;
$z=PHP_INT_MAX+1;
var_dump($x);
var_dump($y);
var_dump($z);

yields:

int(9223372036854775807)
int(9223372036854775806)
float(9.2233720368548E+18)

PHP implicitly converts integers too large to float. And that's where you're basically lost in PHP internals (at least in my opinion), because from here, you'll never know what will happen (without knowing PHP internals, feel free to correct me, though).

Notice this:

$x=PHP_INT_MAX;
$a=(float)($x+1025.0); // 1025 float
$b=(float)($x+1026.0); // 1026 float
$c=(float)($x+1025); // 1025 int
$d=(float)($x+1026); // 1026 int
var_dump($x);
var_dump($a);
var_dump($b);
var_dump($c);
var_dump($d);
var_dump($a==$b);
var_dump($a===$b);
var_dump($c==$d);
var_dump($c===$d);

yields:

int(9223372036854775807)
float(9.2233720368548E+18)
float(9.2233720368548E+18)
float(9.2233720368548E+18)
float(9.2233720368548E+18)
bool(true)
bool(true)
bool(false)
bool(false)

If you add an integer ($x+1026) to PHP_MAX_INT, it is converted to float, and when you add a float ($x+1026.0), it is float, too, of course. But, obviously, they're not the same internally, see the comparisons above.

Bottom line:

  • Don't compare floats for equality
  • Be careful about your casts; (float)($x+1026) is an integer addition, and afterwards casted to float, whereas (float)($x+1026.0) converts $x to float, then adds the float 1026.0, then casts (superfluously) to float.

Edit: additionally, see:

  • Force PHP integer overflow
  • http://php.net/manual/en/language.types.integer.php
like image 102
stef77 Avatar answered Oct 03 '22 03:10

stef77