2

I want to decode a json string to PHP object and then the object back again to json string without losing precision for floats numbers in json.

If you run the sample below the output would be:

JSON:
string '{
 "integer1": 10000,
 "integer2": 100000999499498485845848584584584,
 "float1": 1.121212,
 "float2": 8.226347662837406e+09
}' (length=130)
JSON WITHOUT SPACES [1]:
string '{"integer1":10000,"integer2":100000999499498485845848584584584,"float1":1.121212,"float2":8.226347662837406e+09}' (length=112)
OBJECT:
object(MyObject)[1]
  public 'integer1' => int 10000
  public 'integer2' => float 1.000009994995E+32
  public 'float1' => float 1.121212
  public 'float2' => float 8226347662.8374
JSON FROM OBJECT [2]:
string '{"integer1":10000,"integer2":1.000009994995e+32,"float1":1.121212,"float2":8226347662.8374}' (length=91)

I want string [1] and [2] to be equal or at least not to loose precision. I know that decimal numbers can´t be represented exactly in PHP floats and I know that you can use the option json_decode($json, true, 512, JSON_BIGINT_AS_STRING) to represent number as string in PHP. But using that option I can re-generate the original json from the object.

<?php

$json = <<<EOT
{
 "integer1": 10000,
 "integer2": 100000999499498485845848584584584,
 "float1": 1.121212,
 "float2": 8.226347662837406e+09
}
EOT;

// json_last_error_msg (PHP 5 >= 5.5.0)
if (!function_exists('json_last_error_msg')) {
    function json_last_error_msg() {
        static $errors = array(
            JSON_ERROR_NONE             => null,
            JSON_ERROR_DEPTH            => 'Maximum stack depth exceeded',
            JSON_ERROR_STATE_MISMATCH   => 'Underflow or the modes mismatch',
            JSON_ERROR_CTRL_CHAR        => 'Unexpected control character found',
            JSON_ERROR_SYNTAX           => 'Syntax error, malformed JSON',
            JSON_ERROR_UTF8             => 'Malformed UTF-8 characters, possibly incorrectly encoded'
        );
        $error = json_last_error();
        return array_key_exists($error, $errors) ? $errors[$error] : "Unknown error ({$error})";
    }
}

class MyObject
{
    /** @var  int|float */
    public $integer1;
    /** @var  int|float */
    public $integer2;
    /** @var  int|float */
    public $float1;
    /** @var  int|float */
    public $float2;

    public function __construct($json)
    {
        $this->fromJson($json);
    }

    public function fromJson($json)
    {
        $result = json_decode($json);

        if (json_last_error() != JSON_ERROR_NONE) {
            die('json_decode error: '.json_last_error_msg());
        };

        $this->integer1 = $result->integer1;
        $this->integer2 = $result->integer2;
        $this->float1 = $result->float1;
        $this->float2 = $result->float2;
    }

    public function toJson()
    {
        $json =  json_encode($this);

        if (json_last_error() != JSON_ERROR_NONE) {
            die('json_decode error: '.json_last_error_msg());
        };

        return $json;
    }
}

echo 'JSON: ';
var_dump($json);

echo 'JSON WITHOUT SPACES [1]: ';
var_dump(preg_replace('/\s+/', '', $json));

$object = new MyObject($json);

echo 'OBJECT: ';
var_dump($object);

echo 'JSON FROM OBJECT [2]: ';
var_dump($object->toJson());

I suppose I could add extra metadata to the object and indicate if the string property it was a number in the original json string but then I would have to parse the json by my self and not using the json_decode function.

I have read a lot of posts on this matter but all of them said only how to convert json to object (using string) but not how to convert again that object to the original json as a number in json.

If you want to play with the sample: http://sandbox.onlinephpfunctions.com/code/8d934874c93c4574d886dbbf645a6763ba1ba131

2

1 Answer 1

0

What created your json string?

While it is theoretically correct it is also accurate to a degree of craziness that the normal PHP programmer doesn't need. Those values are simply not representable in 32 bit (or even in 64 bit) PHP memory as floats or integers. By the way, that online link you posted uses 32 bit PHP. When PHP decodes json, it tries to be as accurate as possible when representing the values in memory. Did you notice your integer2 that overflowed was decoded as a float so there was loss of precision, but no overflow? Since it will never be able to represent these insanely accurate values in memory, when you encode the object again, you get a logically similar but different json string.

You should also ask yourself, why do you need the json string to be identical to your original representation? If you really need this degree of accuracy, I would consider using JAVA where you can use higher precision data types (such as BigInteger). PHP is not the best tool for really high accuracy stuff like this. (PHP 5 anyway).

Suggested reading:

Sign up to request clarification or add additional context in comments.

3 Comments

I work on Bitcoin projects. Bitcoin has 8 decimal places, so a relatively small amount of bitcoins produces and 32 bit integer overflow. And being money I need that "insanely accurate values". Really I have no problem with 64 bits systems becuase I represent money values using only integers and there is no problem with precision. The problem is the convertion from the integer to the float. And changing language is not an option.
Another common place where precision matters is GPS encoding. You can lose tens of meters just by chopping off the fourth decimal place.
Precision is important and it's the significant figures (sf) which need to be stored in memory, not just the decimal places (dp). You can store GPS coordinates to the nearest 1.1 meter using 8sf (3 digits for the whole number + 5 dp). Similarly bitcoins using 8dp could be an every day use case but it depends on the number of significant figures. The question above asks about maintaining accuracy with numbers of 33sf (see integer2). That is 10,000,000,000,000,000,000,000,000 (ten septillionas) times more accurate than those 2 examples.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.