Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Issue with PHP array where keys are large integers

What i'm tring to do here is basically start from $stats and get an array $counts containing four arrays, where each array is a pair: key is a timestamp in milliseconds while values are counts. Im using PHP Version 5.3.14 on Windows 7 x64.

Question is: why i'm getting negatives array keys values and how can avoid this? See var_dump() below:

$stats = array();
$stats[] = array(
    'subtype' => 'small_text_message_user',
    'count'   => '6',
    'date'    => '2012-06-03'
);

$stats[] = array(
    'subtype' => 'small_text_message_auto',
    'count'   => '3',
    'date'    => '2012-07-03',
);

$stats = array(
    'subtype' => 'newsletter_auto',
    'count' => '11',
    'date' => '2012-07-16',
);

$counts = array();
$counts['small_text_message_user'] = array();
$counts['small_text_message_auto'] = array();
$counts['newsletter_user']         = array();
$counts['newsletter_auto']         = array();

foreach($data as $stat) :
    $millisecs = 1000 * strtotime($stat['date']);
    $count     = intval($stat['count']);
    $counts[$stat['subtype']][$millisecs] = $count;
    var_dump($millisecs, $count);
endforeach;

var_dump($counts);

And result is wrong:

float 1338674400000

int 6

float 1341266400000

int 3

float 1342389600000

int 11

array (size=4)
  'small_text_message_user' => 
    array (size=1)
      -1355396352 => int 6
  'small_text_message_auto' => 
    array (size=1)
      1236603648 => int 3
  'newsletter_user' => 
    array (size=0)
      empty
  'newsletter_auto' => 
    array (size=1)
      -1935163648 => int 11

An example (which works, actually) is a short piece of code where i'm generating a "dummy" array:

public function createTimestampRangeFromDates(\DateTime $from,
    \DateTime $to = null)
{
    $start = strtotime($from->format('Y-m-d 00:00:00'));
    $limit = strtotime(sprintf('%s 00:00:00', $to ? $to->format('Y-m-d')
        : date('Y-m-d') . ' 00:00:00'));

    return range($start, $limit, 86400);
}

// Test
$start = new \DateTime('2012-06-27');
$end   = new \DateTime('2012-07-07'); // 10 days

$timestamps = createTimestampRangeFromDates($start, $end);
$millisecs  = array_combine($timestamps, array_fill(0, count($timestamps), 0));

var_dump($millisecs);

This works fine and i get large integers as keys:

array (size=11)
  '1340748000000' => int 0
  '1340834400000' => int 0
  '1340920800000' => int 0
  '1341007200000' => int 0
  '1341093600000' => int 0
  '1341180000000' => int 0
  '1341266400000' => int 0
  '1341352800000' => int 0
  '1341439200000' => int 0
  '1341525600000' => int 0
  '1341612000000' => int 0

Oh, if this matters, purpose of this code is to do something like:

$values = array_merge($millisecs, $counts['newsletter_auto']);

That is overriding the dummy array with real values.

Update small test case as suggested by @hakre

$result1 = array();
$result2 = array();
$test    = array('2012-06-03', '2012-07-03', '2012-07-16');

foreach($test as $date) :
    $result1[1000 * strtotime($date)] = 0;
    $result2[] = 1000 * strtotime($date);
endforeach;

var_dump($result1); // Not working
var_dump($result2); // Works

Result shows negative keys for $result1. Issue is only with keys, not for values (as value is converted to float):

array (size=3)
  -1355396352 => int 0
  1236603648 => int 0
  -1935163648 => int 0

array (size=3)
  0 => float 1338674400000
  1 => float 1341266400000
  2 => float 1342389600000
like image 504
gremo Avatar asked Jul 07 '12 22:07

gremo


Video Answer


2 Answers

PHP converts nummeric (so, float or integer) array indexes to integer:

ckruse@lin ~ $ php -r 'var_dump(array(1.2 => "a"));'
array(1) {
  [1]=>
  string(1) "a"
}

So if you have a very large array index, it will get converted to float by PHP when calculated and then back to integer when used as an index. This means that you get an integer overflow with to large indexes.

A solution can be to use string indexes:

ckruse@lin ~ $ php -r 'var_dump(array(((string)1.2) => "a"));'
array(1) {
  ["1.2"]=>
  string(1) "a"
}

Edit: Manual on http://php.net/manual/en/language.types.array.php says:

Floats are also cast to integers, which means that the fractional part will be truncated. E.g. the key 8.7 will actually be stored under 8.

like image 151
ckruse Avatar answered Oct 27 '22 19:10

ckruse


Did you find something on PHP documentation that states a max length for array keys?

The PHP Manual defines it the following: "A key may be either an integer or a string." (Source)

As every data-type, integers as well are limited. That is common in computing. Depending on how integers are being handled by the software or the underlying processor, they can either flip, floor or top.

In your case it goes over to the smallest negative number when the boundary is crossed. I call that flip others might call it overflow or wrap-around.

You can find out about the boundaries with PHP_INI_Min and PHP_INI_MAX.

Take care for string keys, there is also a limit, it normally can not become greater than 2GB. Just saying.

like image 25
hakre Avatar answered Oct 27 '22 20:10

hakre