Saturday, June 28, 2014

Handle the precision lose issue in json_decode of php

Hey all,

I know, it's been months. Literally months since ma last post. And kinda hard to comes back after such delay :/
So i thought I'll do a short post, a quickie.


In one of ma current projects I have a php web service that should decode some json data comming from a database and then encode it again to be used by the backend of the related android app. Sample data would be something like,

  1. {
  2.     "type":"FeatureCollection",
  3.     "generator":"JOSM",
  4.     "features":[
  5.         {
  6.             "type":"Feature",
  7.             "properties":{
  8.             },
  9.             "geometry":{
  10.                 "type":"LineString",
  11.                 "coordinates":[
  12.                     [
  13.                         80.00769225919207,
  14.                         7.0701729968129206
  15.                     ],
  16.                     [
  17.                         80.00769225919207,
  18.                         7.076613500681037
  19.                     ],
  20.                     [
  21.                         79.9820441862658,
  22.                         7.076613500681037
  23.                     ],
  24.                     [
  25.                         79.98203680670133,
  26.                         7.100457726964171
  27.                     ]
  28.                 ]
  29.             }
  30.         }
  31.     ]
  32. }

(Yup something related to latlongs ;) ]

So the problems is that when I do the json_decode on those data, due to the way php handles it, the floating points above loose precision.

ex: 7.0701729968129206 becomes 7.0701729968129
      80.00769225919207 becomes 80.007692259192

since those floating points are used as some hashed keys later on, this loose of precision started giving me one heck of errors. Hence I searched around a bit to see how exactly I can solve it on the server itself, without altering my android app.


There were 2 solutions that were popular all around stackoverflow and related forums.

1) change the precision of php from the system

By adding the following line, we could control the behavior of precision


But it doesnt exactly resolve the issue since, if the original floating point has a lesser precision than the defined, php simply add random numbers to the end (crazzy right?)

ex: 7.0701729968129206 with precision 20 becomes 7.07017299681292064453

2) Changing the json_decode call as follows would help, but only on php > 5.4

$decoded = json_decode($encoded, true, null, JSON_BIGINT_AS_STRING);

So i gave up server side solutions. and thought how exactly json_decode limit those numbers.
Let's look at those two truncated floating points

7.0701729968129    <---- has 13 decimal points
80.007692259192    <---- has 12 decimal points

so the precision isnt the same, then it stuck me

7.0701729968129    <---- has 14 digits
80.007692259192    <---- has 14 digits

yayii, so what it basically does is that make sure that the floating point has only 14 digits by first giving required number of digits for the whole number part and make the rest as decimals.

Now this is pretty easy to handle since many languages has built in support to recreate this behavior.
Here's a short method in java [which I so gratefully found in a SO post :D ]

  1. private double formatNumber(double n){
  2.         MathContext mathContext = new MathContext(14, RoundingMode.HALF_UP);
  3.         return new BigDecimal(n, mathContext).doubleValue();           
  4. }

NOTE: After some observation I found that Rounding Half UP is the correct way to recreate json_decode correctly, and the number of total digits may vary
i got 12 and 14 in two different servers, depend on the php configuration

Hope to write more :)

1 comment: