0

I have an array of tests for a function:

        $testArray = array(
            (object)array(
                "value"     => 12345678901,
                "expected"  => "123456789012"
            ),
            (object)array(
                "value"     => "1234567890",
                "expected"  => false
            ),
            (object)array(
                "value"     => (int)12345678901,
                "expected"  => "123456789012"
            ),
            (object)array(
                "value"     => 1234567890,
                "expected"  => false
            ),
            (object)array(
                "value"     => "12345678901",

                "expected"  => "123456789012"
            ),
            (object)array(
                "value"     => "123456789012",
                "expected"  => "123456789012"
            ),
            (object)array(
                "value"     => "123456789013",
                "expected"  => false
            ),
            (object)array(
                "value"     => "1234567890128",
                "expected"  => "1234567890128"
            ),
            (object)array(
                "value"     => "1234567890127",
                "expected"  => false
            ),
            (object)array(
                "value"     => array(),
                "expected"  => false
            ),
            (object)array(
                "value"     => (object)array(),
                "expected"  => false
            ),
            (object)array(
                "value"     => array("1234567890127"),
                "expected"  => false
            ),
            (object)array(
                "value"     => (object)array("1234567890127"),
                "expected"  => false
            )
        );

I run the array through a foreach loop to see which ones work to test a function, and most of them work. The third one should return 123456789012 but instead it returns "-539222987" when I get the value. Why is this happening when the rest work just fine?

Here is the foreach loop. The validUPC function just looks at the upc and makes sure it is a valid one.

        foreach($testArray as $key => $test) { // Test each value
            $result = App::make("Product")->validUPC($test->value);
            var_dump($result);
            echo "Result from array member $key was: $result\n";
            if($result !== $test->expected) { // Check against expected value
               echo "Test FAILED\n";
               $success = false; // Returns false if any of them failed
            }
            else {
               echo "Test passed\n";
            }
        }

The validUPC function begins like this:

public function validUPC($upc = "non") {
    try {
        // If there is no upc specified, get the product upc
        if($upc == "non") {
            $upc = $this->upc;
        }
        // No arrays allowed
        if(is_array($upc)) {
            return false;
        }
        // Make it a string
        $upc = (string)$upc;
        var_dump($upc);

Which returns something like this:

Product.php:99:float 12345678901
Product.php:101:string '12345678901' (length=11)
CatalogTest.php:70:string '123456789012' (length=12)
Result from array member 0 was: 123456789012 Test passed
Product.php:99:string '1234567890' (length=10)
Product.php:101:string '1234567890' (length=10)
CatalogTest.php:70:boolean false
Result from array member 1 was: Test passed
Product.php:99:int -539222987
Product.php:101:string '-539222987' (length=10)
CatalogTest.php:70:boolean false
Result from array member 2 was: Test FAILED

1 Answers1

3

Without seeing the code in detail, I'll speculate.

On a 32-bit platform, the largest representable integer is 231-1, or 2,147,483,647. The integer you present - 123456789012 - is larger than that. When you give an integer larger than the maximum, PHP, like other languages, overflows the number into a value it can represent. In your case, it's -539222987.

I'd recommend checking that your code's not trying to work with integers larger than PHP_INT_MAX on your platform.


Responding to the comments: PHP converts integers larger than those representable by the available bits to a double. But if you then cast that double to an int, you enter what looks like a wrap-around. On a 64 bit system, for example:

$a = pow(2, 65); // 65 === (PHP_INT_SIZE * 8) + 1
var_dump($a);
$b = (int)$a;
var_dump($b);
$c = $b - 100;
var_dump($c);

Mathematically, we expect $c === 36893488147419103132. It is not. The output is:

float(3.6893488147419E+19)
int(0)
int(-100)

The floating point is approximately the exact value: we've traded precision in the lower bits for the ability to continue working with the value as a number. But then when we force PHP to treat it as an integer, it gives up and truncates it toward 0 - but the behavior is undefined, so don't rely on it always being exactly 0: all hell has broken loose at point.

Worse, there is no warning, no notice raised. So how do you guard your code against this scenario? I urge use of strict_types. When you expect to be working with precisely an int, define your functions to take them. If you then calculate a value that over- or underflows, you'll get a float, which will pass into your strictly int method, which then will notify you.

If you need to work with big integers, there are libraries for that.

bishop
  • 37,830
  • 11
  • 104
  • 139