PHP7.1 json_encode() Float Issue

我只是一个虾纸丫 提交于 2019-11-27 11:11:28

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}

As a plugin developer I don't have general access to the php.ini settings of a server. So, based on Machavity's answer I wrote this small piece of code that you can use in your PHP script. Simply put it on top of the script and json_encode will keep working as usual.

if (version_compare(phpversion(), '7.1', '>=')) {
    ini_set( 'serialize_precision', -1 );
}

In some cases it is necessary to set one more variable. I am adding this as a second solution because I am not sure if the second solution works fine all cases where the first solution has proven to work.

if (version_compare(phpversion(), '7.1', '>=')) {
    ini_set( 'precision', 17 );
    ini_set( 'serialize_precision', -1 );
}

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.

The other solutions didn't work for me. Here's what I had to add at the beginning of my code execution:

if (version_compare(phpversion(), '7.1', '>=')) {
    ini_set( 'precision', 17 );
    ini_set( 'serialize_precision', -1 );
}

I was encoding monetary values and had things like 330.46 encoding to 330.4600000000000363797880709171295166015625. If you don't wish to, or can't, change the PHP settings and you know the structure of the data in advance there is a very simple solution that worked for me. Simply cast it to a string (both the following do the same thing):

$data['discount'] = (string) $data['discount'];
$data['discount'] = '' . $data['discount'];

For my use case this was a quick and effective solution. Just note that this means when you decode it back from JSON it will be a string since it'll be wrapped in double quotes.

You could change the [max] => 472.185 from a float into a string ([max] => '472.185') before the json_encode(). As json is a string anyway, converting your float values to strings before json_encode() will maintain the value you desire.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!