PHP7.1 json_encode() Float Issue
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 andserialize()
/var_export()
uses PG(serialize_precision) which set to 17 be default to be more precise. Sincejson_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 in 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 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
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.