6

I've stumbled across a weird bug/problem.

I have a MySQL table with a column filled with numbers (BIGINT). These numbers are too big for a regular 32 bit integer, so PHP will cast them to a string on 32-bit. This gives the correct result every time.

When run on 64 bit PHP and not forcibly cast to a string with $variable = (string)$variable, the result will sometimes be decremented by 1, such that a number like 1293203059233 becomes 1293203059232. This is no good obviously. The odd thing is that I cannot see any pattern.

It does not happen randomly such that one row from MySQL is decremented sometimes and sometimes not, but such that the same integers/rows are always decremented, and always by 1.

What could cause this? I use json_encode to convert stdClass-objects or arrays() to text, then send them by regular HTTP responses.

The rows are retrieved by mysqli using prepared statements such as :

$stmt = $sql->prepare->("SELECT BIGNUMBER FROM table WHERE SOMEID = ?");
$stmt->bind_result($bignumber);
$stmt->bind_param("i",$someid);
$stmt->execute();
$stmt->fetch();
$stmt->close();

$obj = new stdClass();
$obj->number = $bignumber;

echo json_encode($obj);

I have verified that all the integers are correct when browsing the database table.

Some examples (these are the actual values):

without cast to string:

10205160559939609 -> 10205160669939608 // bad

with:

10205160559939609 -> "10205160559939609" // good

without cast to string:

10154493437278508 -> 10154493437278508 // good (?)

with:

10154493437278508 -> "10154493437278508" // good

Edit: I did a error_log test pre-json_encode to test, yielding:

as Strng: (used error_log((string)$number);)
10205160559939609
as int: (used error_log($number);)
10205160559939609

Which would indicate that php does get the correct value, and that the error occurs in either php json_encode or in the browser's decode method.

nickdnk
  • 4,010
  • 4
  • 24
  • 43
  • Can you keep track of and list the numbers, see if anyone else can spot a pattern? – Styphon Oct 06 '15 at 11:55
  • 2
    Please see this answer http://stackoverflow.com/a/9622041/1866988 – Fedir Petryk Oct 06 '15 at 11:56
  • it's an exponential rounding error. – Martin Oct 06 '15 at 11:59
  • @Martin care to elaborate? If PHP can't correctly do math with such relatively small integers... then what – nickdnk Oct 06 '15 at 12:00
  • Possible duplicate except for the fact that these numbers are within PHPs regular 64 bit range, which is 9223372036854775807 – nickdnk Oct 06 '15 at 12:02
  • 1
    @Martin `PHP_INT_MAX` for 64bit PHP is `9223372036854775807`, which is larger than `10205150669939609`. – Siguza Oct 06 '15 at 12:03
  • 1
    My guess would be that it's related to the [known issue with json_encode and large integers](http://stackoverflow.com/questions/30589965/json-decode-and-json-encode-long-integers-without-loosing-data)... Can you reproduce it just with a specific integer and the json_encode? – Matt Gibson Oct 06 '15 at 12:05
  • Do you see any even/odd pattern? – Joaquín O Oct 06 '15 at 12:07
  • @nickdnk Are you using PHP on Windows? – Siguza Oct 06 '15 at 12:07
  • 1
    @MattGibson These numbers are also bigger than PHP_MAX_INT, it seems. At least in the question you linked. – nickdnk Oct 06 '15 at 12:07
  • No, it's Amazon EC2 default. 64 bit PHP. – nickdnk Oct 06 '15 at 12:07
  • With reference to @MattGibson 's link, does the same +/- 1 occur when the values are not `json_encoded`? Take them direct from the DB to the PHP output, – Martin Oct 06 '15 at 12:08
  • @nickdnk Does `json_encode([10205150669939609])` yield the correct value? – Siguza Oct 06 '15 at 12:08
  • I will try that @Martin. Hold on. – nickdnk Oct 06 '15 at 12:08
  • @JoaquínO No pattern. The most pattern you get is from above examples. – nickdnk Oct 06 '15 at 12:09
  • Bear in mind this might be very specific to a PHP version/platform/architecture. There were quite a few bugs around big integers and json encoding, from what I recall. – Matt Gibson Oct 06 '15 at 12:10
  • @nickdnk Yeah, I just don't know what json_encode does with numbers. Bear in mind it's targeted for JavaScript, and in JavaScript, all numbers are actually floating point, as I understand it, even if they look like integers... – Matt Gibson Oct 06 '15 at 12:12
  • 1
    I can just cast them to string. The result is fine as a string in my case, fortunately. It just worries me that later down the line some of my maths will go wrong if I encounter a large integer at some point. Give me a minute and I'll have some results from the json_encode test. – nickdnk Oct 06 '15 at 12:13
  • @nickdnk Did you try my `json_encode([10205150669939609])`? (Just checking whether flat values work.) – Siguza Oct 06 '15 at 12:15
  • Are you sure that it's an integer? Have you checked (`var_dump()`)? I can see how a float might do this kind of thing. – Simba Oct 06 '15 at 12:16
  • I guess I have an explanation for you.. `json_encode` turns your integers (17 digits) into floats.. Then, following php manual here http://php.net/manual/en/language.types.float.php you can see that there is some rounding error after a 16 digits precision.. Helps? – Joaquín O Oct 06 '15 at 12:28
  • Simply typing in 10205150669939609 in the chrome console will give you the number 10205150669939608 printed out (rounding effect). I guess integers this big aren't valid in JS so neither should they be in JSON. I'd use strings if I had values that large. – andrrs Oct 06 '15 at 12:29
  • 1
    @andrrs [JavaScript ≠ PHP](https://3v4l.org/CDHe5) – Siguza Oct 06 '15 at 12:32
  • 1
    If you're going to be using these numbers in JavaScript, you definitely need to bear in mind that JavaScript numbers are *all* floating point. Even 3 is a floating point number in JavaScript. There will likely be precision errors the bigger the numbers get. [This issue](http://inessential.com/2014/04/13/vesper_sync_diary_13_unlucky_13) sprang to mind when I read your post. – Matt Gibson Oct 06 '15 at 12:33
  • 1
    @MattGibson That's just downright horrible news. – nickdnk Oct 06 '15 at 12:33
  • 1
    @nickdnk That's JavaScript :) But really, it all depends what these numbers really represent. If they're actually numbers, then do you need them to be so precise that 64-bit floating point has problems representing them? If they're not numbers, they should probably be strings. – Matt Gibson Oct 06 '15 at 12:34
  • @nickdnk Maybe I misinterpreted, but I just copied one of your numbers... – andrrs Oct 06 '15 at 12:38
  • @andrrs Sorry, you did. I was too fast there. Maybe JavaScript is just useless with numbers this big. – nickdnk Oct 06 '15 at 12:39
  • In this case they represent facebook app scoped user ids, which are fine as strings. I was just wondering where this error came from. @MattGibson – nickdnk Oct 06 '15 at 12:40
  • Now I don't know how to answe this question. As @andrrs pointed out, a simple console.log(10205150669939609) will give the wrong result, so this is entirely JavaScripts fault and has nothing to do with PHP. – nickdnk Oct 06 '15 at 12:45
  • @nickdnk If I were you, I'd just answer it with the results you've found; it'll probably help the next person along who's running into this and can't quite figure out why... – Matt Gibson Oct 06 '15 at 12:47
  • True, but I wouldnt want to steal credit. @andrrs please post the answer – nickdnk Oct 06 '15 at 12:51
  • 1
    This should not be closed as duplicate, as the "answered duplicate" has nothing to do with this problem. It deals with integers bigger than PHP_INT_MAX, which this does not. – nickdnk Oct 10 '15 at 12:24
  • Indeed. This question isn't a duplicate as the problem is the representation of large (but below PHP_INT_MAX) numbers in JavaScript, not how PHP handles them. – Matt Gibson Oct 19 '15 at 09:26

1 Answers1

2

Simply typing in 10205150669939609 in the chrome console will give you the number 10205150669939608 printed out (rounding effect). I guess integers this big aren't valid in JS so neither should they be in JSON. I'd use strings if I had values that large.

andrrs
  • 2,289
  • 3
  • 17
  • 25
  • from all the discussion in the comments I would hope that this answer would have been a bit , well, more educational. – Martin Oct 06 '15 at 12:54
  • such as clarifying that the issue is not with PHP itself, as the PHP_INT_MAX value is larger than the int used, and that the issue is specifically `JSON` encapsulation because javascript deems all numbers to be floating rather than integers. So it's a rounding error when a float (DOUBLE) is then brought back into being an INT type in PHP. – Martin Oct 06 '15 at 12:57
  • @Martin - "brought back into being an INT type in PHP.". Do you mean "in JavaScript"? PHP does not have any problems with my numbers. – nickdnk Oct 06 '15 at 12:59
  • @nickdnk PHP has no problems, but the numerical value is passed to JSON and stored in the JSON system and that is where the fault occurs and then when the number is passed back to the PHP (ie non-javascript) system then the fault is passed with it, due to JS using floats rather than INTs. :) – Martin Oct 06 '15 at 13:00
  • Also, @andrrs, please know that JSON is not type-aware. It will print any integer size correctly as integer, but JavaScript will fail to deserialize it. – nickdnk Oct 06 '15 at 13:01
  • https://cube-drone.com/comics/c/if-programming-languages-were-vehicles-iii – Martin Oct 06 '15 at 13:03