4

How to check if double value is larger or smaller then PHP_INT_MAX:

<?php

$result = (double)PHP_INT_MAX;
$result += 100.0;

if ($result > PHP_INT_MAX) {
    echo "result is > then PHP_INT_MAX";
} else {
    echo "result is <= then PHP_INT_MAX";
}

And the output is:

result is <= then PHP_INT_MAX

which is incorrect.

Then I tried with:

<?php

$result = (double)PHP_INT_MAX;
$result += 100.0;
//$result -= 100.0;


if ((int) $result < 0) {
    echo "Solution 1: result is > then PHP_INT_MAX\n";
} else {
    echo "Solution 1: result is <= then PHP_INT_MAX\n";
}

if (!filter_var(
    $result,
    FILTER_VALIDATE_INT,
    array(
        'options' => array(
            'min_range' => 0,
            'max_range' => PHP_INT_MAX,
        )
    )
)) {
    echo "Solution 2: result is > then PHP_INT_MAX\n";
} else {
    echo "Solution 2: result is <= then PHP_INT_MAX\n";
}

And it seemed to work:

Solution 1: result is > then PHP_INT_MAX
Solution 2: result is > then PHP_INT_MAX

But then I did this (decrease $result for 100):

<?php

$result = (double)PHP_INT_MAX;
//$result += 100.0;
$result -= 100.0;


if ((int) $result < 0) {
    echo "Solution 1: result is > then PHP_INT_MAX\n";
} else {
    echo "Solution 1: result is <= then PHP_INT_MAX\n";
}

if (!filter_var(
    $result,
    FILTER_VALIDATE_INT,
    array(
        'options' => array(
            'min_range' => 0,
            'max_range' => PHP_INT_MAX,
        )
    )
)) {
    echo "Solution 2: result is > then PHP_INT_MAX\n";
} else {
    echo "Solution 2: result is <= then PHP_INT_MAX\n";
}

And output is wrong:

Solution 1: result is > then PHP_INT_MAX
Solution 2: result is > then PHP_INT_MAX

Does anybody knows what is going on, and how to reliably check the value against PHP_INT_MAX?

Tested on Linux, 64bit system, PHP 7.3.18.

Thanks!

UPDATE:

  1. After reading all the comments, answers and after lots of experimentation:

In order to roughly detect when double value will go over PHP_INT_MAX you can use:

!filter_var(number_format($result, 0, '.', ''), FILTER_VALIDATE_INT)
$result >= PHP_INT_MAX
(int) $result < 0

(This one assumes that it is expected that $result will be >=0 to start with).

Checkout this example, where I am testing 20,000 numbers around PHP_INT_MAX:

<?php

function from_scientific_notation($value)
{
    $decimalNotation = rtrim(sprintf('%f', $value), '0');
    return rtrim($decimalNotation, '.');
}


for ($i = -10000; $i <= 10000; $i += 1) {
    $result = (double) PHP_INT_MAX + (double) $i;

    if (!filter_var(number_format($result, 0, '.', ''), FILTER_VALIDATE_INT)) {
        echo "S1:";
    }

    if ($result >= PHP_INT_MAX) {
        echo "S2:";
    }

    if ((int) $result < 0) {
        echo "S3:";
    }

    echo "\nPHP_INT_MAX + $i = " .from_scientific_notation($result) . "\n";
}

Output:

...

PHP_INT_MAX + -515 = 9223372036854774784

PHP_INT_MAX + -514 = 9223372036854774784

PHP_INT_MAX + -513 = 9223372036854774784
S1:S2:S3:
PHP_INT_MAX + -512 = 9223372036854775808
S1:S2:S3:
PHP_INT_MAX + -511 = 9223372036854775808
....

Please note loss of precision, the number PHP_INT_MAX - 512 is detected to be too larger then PHP_INT_MAX. Depending on the situation this inaccuracy might be or might not be acceptable.

  1. But really, the only correct way of working with such large numbers would be to use libraries like bcmath or gmp (as mentioned in the answer below). But again - that might require a major refactor which might be out of the question.
DamirR
  • 1,696
  • 1
  • 14
  • 15
  • why do you cast `PHP_INT_MAX` to `double`? – Raptor Nov 18 '20 at 08:49
  • 1
    I think [this answer](https://stackoverflow.com/a/31774255/4205384) might prove an interesting read. – El_Vanja Nov 18 '20 at 08:53
  • Definition of `PHP_INT_MAX` in PHP Documentation: *The largest integer supported in this build of PHP. Usually int(2147483647) in 32 bit systems and int(9223372036854775807) in 64 bit systems.* If you add 100 to the value, it will still be the largest possible value in your system (i.e. no change of value). Thus, the first snippet is CORRECT, as the values are EQUAL. – Raptor Nov 18 '20 at 08:55
  • Doesn't it make `PHP_INT_MAX + 100` to overflow memory and just make bits conversion making _less_ than PHP_INT_MAX? – Justinas Nov 18 '20 at 08:56
  • There is a physical limit to how many bits of any register can hold 1 at the same time. On a 64-bit platform, the largest unsigned integer is 9,223,372,036,854,775,807. If you add `1` to this, the value will _overflow_ to `0` or beyond if you add more, but will always be positive for unsigned values. – Ro Achterberg Nov 18 '20 at 08:56
  • You don't need a max for `FILTER_VALIDATE_INT` . If it's within the valid int range it's an int otherwise it's not an int (even if it is an integer number mathematically speaking) – apokryfos Nov 18 '20 at 09:03
  • @Raptor doubles in php can be must larger numbers then ints (on my system `PHP_FLOAT_MAX`=`1.7976931348623E+308` while `PHP_INT_MAX`=`9223372036854775807`) . So first segment is not correct since I am working with doubles that can hold much larger values then ints. – DamirR Nov 18 '20 at 09:16
  • @Justinas No, because I am working with doubles (can hold much larger values then ints). – DamirR Nov 18 '20 at 09:17
  • @RoAchterberg That is why I am working with doubles. And this is why I used this test: `(int) $result < 0` in order to cause the overflow and detect too big value in this way. It didn't work. – DamirR Nov 18 '20 at 09:19
  • 1
    Please see my answer on how to do this. – Ro Achterberg Nov 18 '20 at 09:20
  • @apokryfos Just tried it. It works for: `$result = (double)PHP_INT_MAX; $result += 100.0;` but not for: `$result = (double)PHP_INT_MAX; $result -= 100.0;` – DamirR Nov 18 '20 at 09:21
  • @RoAchterberg Thx. You are right in a way that bcmath (or similar lib) should be used when dealing with such large numbers - but I can't introduce it in current legacy code. :( – DamirR Nov 18 '20 at 09:31

2 Answers2

2

Use the BC Math Functions. See comments for explanation.

<?php

$int = PHP_INT_MAX;

var_dump($int); // int(9223372036854775807)

$larger = bcadd($int, '100');

var_dump($larger); // string(19) "9223372036854775907"

// bccomp() will return 1 if first argument is larger than second.
var_dump(bccomp($larger, $int)); // int(1)

UPDATE

Updated demo, showing how bcadd() shows the intended output of your initial question. This outputs:

result is > then PHP_INT_MAX
result is <= then PHP_INT_MAX

Code:

<?php

$result = (double)PHP_INT_MAX;

// Add 100 to PHP_INT_MAX.
// Use sprintf() to prevent type juggling from formatting the float
// using scientific syntax, which is unsupported by bcadd().
$result = bcadd(sprintf('%f', $result), '100.0');

if (bccomp($result, sprintf("%f", (double)PHP_INT_MAX)) === 1) {
    echo "result is > then PHP_INT_MAX\n";
} else {
    echo "result is <= then PHP_INT_MAX\n";
}


// Subtract 200 from first result, making it less than PHP_INT_MAX.
$result = bcsub(sprintf('%f', $result), '200.0');

if (bccomp($result, sprintf("%f", (double)PHP_INT_MAX)) === 1) {
    echo "result is > then PHP_INT_MAX\n";
} else {
    echo "result is <= then PHP_INT_MAX\n";
}
Ro Achterberg
  • 2,504
  • 2
  • 17
  • 17
  • Please note how you _cannot_ cast `$larger` back to an int and expect to see 9223372036854775907. When dealing with larger numbers, you're basically stuck using strings, and the BC ("basic calculator") set of functions is your friend for this. – Ro Achterberg Nov 18 '20 at 09:02
  • Thank you for the answer, but this solution depends on using `bcadd` and `bcsub` - which I can't use. The `$result` is provided to me by the legacy code in which I can't introduce bcmath (at least not in a safe, simple and fast way). And if I don't use `bcadd` or `bcsub` the `bccomp` will not work properly. :( – DamirR Nov 18 '20 at 09:29
  • I'm afraid there is no 'simple and fast' way to deal with this, without refactoring some code to use the BC functions. You can't cheat physics! – Ro Achterberg Nov 18 '20 at 09:31
  • I still have some hope left. I just wanted to compare double value with PHP_INT_MAX (which is well in the allowed interval for doubles). And I don't even care about +/1 errors: since `(double)10` can actually be `9.9999` but to be wrong by `100` is just too much! :( – DamirR Nov 18 '20 at 09:34
  • I lost the hope! :D I updated my question. Anyway two options: use one of improvised methods which come with some inaccuracy (which might be, or might not be acceptable). Or refactor the code to use bcmath or gmp (which, as well, might be or might not be possible). – DamirR Nov 18 '20 at 16:16
1

Well the "easy" thing to do is:

filter_var(number_format($result, 0,'.',''), FILTER_VALIDATE_INT);

Where $result is a floating point number.

However the value will need to be significantly different than PHP_INT_MAX for this to work due to the way floating point numbers lose accuracy. Values close to PHP_INT_MAX are rather unpredictable.

There is a big red warning here you need to read carefully before working with floating point numbers but the point is the accuracy is not unlimited and the larger the number the smaller the accuracy gets.

The IEE 754 wikipedia page is also a good read, but the focus here is that a double precision floating point number has about 16 decimal digits so anything that has more than 16 significant digits will not be accurate. PHP_INT_MAX on 64 bit machines is about 19 digits so will lose about 3 significant digits of accuracy.

apokryfos
  • 38,771
  • 9
  • 70
  • 114
  • Thx for the answer and links. Actually this method works (at least on my machine) the same as `$result >= PHP_INT_MAX` or `(int)$result<0)`. So in short - they all work as long as we count in the loss of precision in last few digits. :( – DamirR Nov 18 '20 at 16:01