Bug #74221 [Com]: Float precision is altered when encoding to JSON for floats 0.99... and under
| From: | bryn at imgur dot com | Date: | Tue, 07 Mar 2017 23:41:53 +0000 |
| Subject: | Bug #74221 [Com]: Float precision is altered when encoding to JSON for floats 0.99... and under | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-207735@lists.php.net to get a copy of this message | ||
Edit report at https://bugs.php.net/bug.php?id=74221&edit=1
ID: 74221
Comment by: bryn at imgur dot com
Reported by: bryn at imgur dot com
Summary: Float precision is altered when encoding to JSON for
floats 0.99... and under
Status: Open
Type: Bug
Package: JSON related
Operating System: Ubuntu 16.04.2 LTS
PHP Version: 7.1.2
Block user comment: N
Private report: N
New Comment:
Aha! I see what you are saying about
-1 now. The behavior is still wonky IMHO though
and really breaks down with precision beyond 16. I'm going to let the filed issue stand for now
and let you folks decide on "the right thing".
Previous Comments:
------------------------------------------------------------------------
[2017-03-07 23:40:57] nikic@php.net
The precision of floating point numbers in json_encode() has always been represented by a global ini
setting. In PHP < 7.1 this was the precision setting, in PHP >= 7.1 it is the
serialize_precision setting.
If serialize_precision is set to -1 values will always be accurately represented, while using a
minimal representation. If serialize_precision is set to a sufficiently high value (>= 17),
values will always be accurately represented, but may use a non-minimal representation (as can be
observed in your example).
If you wish to control the precision on a per-key level you can use serialize_precision=-1 in
combination with upfront rounding of values to the desired precision.
The problem you are experiencing is caused by serialize_precision being set to an excessively and
meaninglessly high value of ~53. Your problem can be avoided by setting serialize_precision to a
more reasonable value, or more simply, by leaving it at the default value.
------------------------------------------------------------------------
[2017-03-07 23:12:42] bryn at imgur dot com
@nikic regardless of serialize precision, the encoded JSON value should represent the original
value. The 5.x branch gets the expected result from my example. The 7.x branch does not. Also,
desired precision might be different for two keys in the same JSON object. Having to juggle a global
ini setting is not a very good solution.
------------------------------------------------------------------------
[2017-03-07 23:06:13] nikic@php.net
You can restore the previous behavior by setting serialize_precision to the value of precision,
which is 14 by default. Alternatively and preferably, you can set serialize_precision to -1. In this
case PHP will automatically determine the minimal precision necessary to still accurately represent
the number.
------------------------------------------------------------------------
[2017-03-07 22:49:10] bryn at imgur dot com
Description:
------------
The change to using serialize_precision in json_encode() may have had some
side affects. Floating point numbers under 1.0 get a different precision than other floating point
numbers and the precision for those numbers can be unpredictable.
I noticed that var_export() is behaving the in the same way, but that is far less
critical because it's not used as a data exchange format.
At the moment, I have a single workaround for a single case - When encoding a JSON object only
dealing with the exact same precision values, setting serialize_precision to 2 gives
the expected result. That won't work for objects with mixed precision though.
Test script:
---------------
<?php
# Getting the precision on the cli seems to change it.
# Before these two lines were added, I was getting 53 points of precision.
# After, it adhered to the value for serialize_precision and stayed there.
#print 'Precision is set to '.ini_get('precision').PHP_EOL;
#print 'Serialize Precision is set to '.ini_get('serialize_precision').PHP_EOL;
$data = array(
0.35,
(float)0.35,
round(0.35, 2),
0.15,
1.25,
1.23,
1.0,
0.9,
4,
);
print 'var_export: '.PHP_EOL.var_export($data, 1).PHP_EOL;
print 'json_encode: '.PHP_EOL.json_encode($data, JSON_PRESERVE_ZERO_FRACTION).PHP_EOL;
print 'print_r'.PHP_EOL;
print_r($data);
Expected result:
----------------
var_export:
array (
0 => 0.35,
1 => 0.35,
2 => 0.35,
3 => 0.15,
4 => 1.25,
5 => 1.23,
6 => 1.0,
7 => 0.9,
8 => 4,
)
json_encode:
[0.35,0.35,0.35,0.15,1.25,1.23,1.0,0.9,4]
print_r
Array
(
[0] => 0.35
[1] => 0.35
[2] => 0.35
[3] => 0.15
[4] => 1.25
[5] => 1.23
[6] => 1
[7] => 0.9
[8] => 4
)
Actual result:
--------------
var_export:
array (
0 => 0.34999999999999997779553950749686919152736663818359375,
1 => 0.34999999999999997779553950749686919152736663818359375,
2 => 0.34999999999999997779553950749686919152736663818359375,
3 => 0.1499999999999999944488848768742172978818416595458984375,
4 => 1.25,
5 => 1.229999999999999982236431605997495353221893310546875,
6 => 1.0,
7 => 0.90000000000000002220446049250313080847263336181640625,
8 => 4,
)
json_encode:
[0.34999999999999997779553950749686919152736663818359375,0.34999999999999997779553950749686919152736663818359375,0.34999999999999997779553950749686919152736663818359375,0.1499999999999999944488848768742172978818416595458984375,1.25,1.229999999999999982236431605997495353221893310546875,1.0,0.90000000000000002220446049250313080847263336181640625,4]
print_r
Array
(
[0] => 0.35
[1] => 0.35
[2] => 0.35
[3] => 0.15
[4] => 1.25
[5] => 1.23
[6] => 1
[7] => 0.9
[8] => 4
)
------------------------------------------------------------------------
--
Edit this bug report at https://bugs.php.net/bug.php?id=74221&edit=1