编码和解码使用PHP在json中浮动而不会丢失精度

时间:2015-05-08 12:26:29

标签: php json floating-point

我想将json字符串解码为PHP对象,然后将该对象再次转换回json字符串,而不会丢失json中浮点数的精度。

如果你运行下面的示例,输出将是:

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)

我希望字符串[1]和[2]相等或至少不会松散精度。 我知道十进制数字不能在PHP浮点数中完全表示,我知道您可以使用选项json_decode($json, true, 512, JSON_BIGINT_AS_STRING)在PHP中将数字表示为字符串。但是使用该选项我可以从对象重新生成原始json。

<?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());

我想我可以向对象添加额外的元数据,并指示字符串属性是否是原始json字符串中的数字但是我必须通过自己解析json而不使用json_decode函数。

我已经阅读了很多关于此事的帖子,但他们都只说了如何将json转换为对象(使用字符串),而不是如何将该对象再次转换为原始json作为json中的数字。

如果您想使用示例:http://sandbox.onlinephpfunctions.com/code/8d934874c93c4574d886dbbf645a6763ba1ba131

1 个答案:

答案 0 :(得分:0)

是什么创建了你的json字符串?

虽然它在理论上是正确的,但它对于普通PHP程序员不需要的一定程度的疯狂也是准确的。这些值在32位(甚至64位)PHP内存中无法表示为浮点数或整数。顺便说一句,您发布的在线链接使用32位PHP。当PHP解码json时,它会在表示内存中的值时尽可能准确。您是否注意到溢出的integer2被解码为float,因此精度损失,但没有溢出?由于它永远无法在内存中表示这些疯狂准确的值,因此当您再次对对象进行编码时,会得到逻辑上相似但不同的json字符串。

您还应该问问自己,为什么需要json字符串与原始表示相同?如果你真的需要这种程度的准确性,我会考虑使用JAVA,你可以使用更高精度的数据类型(如BigInteger)。 PHP不是这种真正高精度的东西的最佳工具。 (无论如何,PHP 5)。

建议阅读: