PHP7.1 json_encode() Float Issue

后端 未结 11 958
既然无缘
既然无缘 2020-11-27 13:00

This isn\'t a question as it is more of a be aware. I updated an application that uses json_encode() to PHP7.1.1 and I was seeing an issue with floats being cha

相关标签:
11条回答
  • 2020-11-27 13:20

    It seems like the problem occurs when serialize and serialize_precision are set to different values. In my case 14 and 17 respectively. Setting them both to 14 resolved the issue, as did setting serialize_precision to -1.

    The default value of serialize_precision was changed to -1 as of PHP 7.1.0 which means "an enhanced algorithm for rounding such numbers will be used". But if you are still experiencing this issue, it may be because you have a PHP config file in place from a prior version. (Maybe you kept your config file when you upgraded?)

    Another thing to consider is if it makes sense to be using float values at all in your case. It may or may not make sense to use string values containing your numbers to ensure the proper number of decimal places is always retained in your JSON.

    0 讨论(0)
  • 2020-11-27 13:23

    I solved this by setting both precision and serialize_precision to the same value (10):

    ini_set('precision', 10);
    ini_set('serialize_precision', 10);
    

    You can also set this in your php.ini

    0 讨论(0)
  • 2020-11-27 13:23

    I had the same problem but only serialize_precision = -1 did not solve the problem. I had to do one more step, to update the value of precision from 14 to 17 (as it was set on my PHP7.0 ini file). Apparently, changing the value of that number changes the value of the computed float.

    0 讨论(0)
  • 2020-11-27 13:23
    $val1 = 5.5;
    $val2 = (1.055 - 1) * 100;
    $val3 = (float)(string) ((1.055 - 1) * 100);
    var_dump(json_encode(['val1' => $val1, 'val2' => $val2, 'val3' => $val3]));
    
    {
      "val1": 5.5,
      "val2": 5.499999999999994,
      "val3": 5.5
    }
    
    0 讨论(0)
  • 2020-11-27 13:25

    Store it as a string with the exact precision that you need by using number_format, then json_encode it using the JSON_NUMERIC_CHECK option:

    $foo = array('max' => number_format(472.185, 3, '.', ''));
    print_r(json_encode($foo, JSON_NUMERIC_CHECK));
    

    You get:

    {"max": 472.185}
    

    Note that this will get ALL numeric strings in your source object to be encoded as numbers in the resulting JSON.

    0 讨论(0)
  • 2020-11-27 13:29

    This drove me nuts for a bit until I finally found this bug which points you to this RFC which says

    Currently json_encode() uses EG(precision) which is set to 14. That means that 14 digits at most are used for displaying (printing) the number. IEEE 754 double supports higher precision and serialize()/var_export() uses PG(serialize_precision) which set to 17 be default to be more precise. Since json_encode() uses EG(precision), json_encode() removes lower digits of fraction parts and destroys original value even if PHP's float could hold more precise float value.

    And (emphasis mine)

    This RFC proposes to introduce a new setting EG(precision)=-1 and PG(serialize_precision)=-1 that uses zend_dtoa()'s mode 0 which uses better algorigthm for rounding float numbers (-1 is used to indicate 0 mode).

    In short, there's a new way to make PHP 7.1 json_encode use the new and improved precision engine. In php.ini you need to change serialize_precision to

    serialize_precision = -1
    

    You can verify it works with this command line

    php -r '$price = ["price" => round("45.99", 2)]; echo json_encode($price);'
    

    You should get

    {"price":45.99}
    
    0 讨论(0)
提交回复
热议问题