Bug #74221 [Com]: Float precision is altered when encoding to JSON for floats 0.99... and under

From: 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

« previous php.bugs (#207735) next »