PHP7.1 json_encode() Float Issue

PhpJsonPrecisionPhp 7.1

Php Problem Overview


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 changed to sometimes extend out 17 digits. According to documentation, PHP 7.1.x started to use serialize_precision instead of precision when encoding double values. I'm guessing this caused an example value of

> 472.185

to become

>472.18500000000006

after that value went through json_encode(). Since my discovery, I have reverted back to PHP 7.0.16 and I no longer have the issue with json_encode(). I also tried to update to PHP 7.1.2 before reverting back to PHP 7.0.16.

The reasoning behind this question does stem from https://stackoverflow.com/questions/3726721/php-floating-number-precision, however the end all reason for this is because of the change from precision to serialize_precision usage in json_encode().

If anyone does know of a solution to this problem, I'd be more than happy to listen in on the reasoning/fix.

Excerpt from multidimensional array (before):

[staticYaxisInfo] => Array
                    (
                        [17] => stdClass Object
                            (
                                [variable_id] => 17
                                [static] => 1
                                [min] => 0
                                [max] => 472.185
                                [locked_static] => 1
                            )

                    )

and after going through json_encode()...

"staticYaxisInfo":
			{
				"17":
				{
					"variable_id": "17",
					"static": "1",
					"min": 0,
					"max": 472.18500000000006,
					"locked_static": "1"
				}
			},

Php Solutions


Solution 1 - Php

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}

Solution 2 - Php

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 );
}

Solution 3 - Php

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

Solution 4 - Php

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.

Solution 5 - Php

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.

Solution 6 - Php

On php 7.2.32 the solution was to set in php.ini:

precision=10
serialize_precision=10

Solution 7 - Php

As for me the problem was when JSON_NUMERIC_CHECK as second argument of json_encode () was passed, which casting all (not only integer) numbers type to int.

Solution 8 - Php

$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
}

Solution 9 - Php

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.

Solution 10 - Php

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.

Solution 11 - Php

Same issue. On PHP 7.4 I tried different solutions but only this combination worked for me:

precision = 14
serialize_precision = 14

Solution 12 - Php

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionGwi7d31View Question on Stackoverflow
Solution 1 - PhpMachavityView Answer on Stackoverflow
Solution 2 - PhpalevView Answer on Stackoverflow
Solution 3 - Phpwhatever_saView Answer on Stackoverflow
Solution 4 - PhptexelateView Answer on Stackoverflow
Solution 5 - PhpAlin PopView Answer on Stackoverflow
Solution 6 - Phpuser15572187View Answer on Stackoverflow
Solution 7 - PhpAcunaView Answer on Stackoverflow
Solution 8 - PhpB.AsselinView Answer on Stackoverflow
Solution 9 - PhppasqalView Answer on Stackoverflow
Solution 10 - PhpCode CommanderView Answer on Stackoverflow
Solution 11 - PhpАндрей ЯндугановView Answer on Stackoverflow
Solution 12 - PhpEverett StaleyView Answer on Stackoverflow