php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #74221 Float precision is altered when encoding to JSON for floats 0.99... and under
Submitted: 2017-03-07 22:49 UTC Modified: 2017-03-11 20:16 UTC
Votes:2
Avg. Score:4.5 ± 0.5
Reproduced:1 of 2 (50.0%)
Same Version:1 (100.0%)
Same OS:1 (100.0%)
From: bryn at imgur dot com Assigned: bukka (profile)
Status: Not a bug Package: JSON related
PHP Version: 7.1.2 OS: Ubuntu 16.04.2 LTS
Private report: No CVE-ID: None
 [2017-03-07 22:49 UTC] 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
)

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2017-03-07 23:06 UTC] 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 23:12 UTC] 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:40 UTC] 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:41 UTC] bryn at imgur dot com
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".
 [2017-03-11 20:16 UTC] bukka@php.net
-Status: Open +Status: Not a bug -Assigned To: +Assigned To: bukka
 [2017-03-11 20:16 UTC] bukka@php.net
This has been already decided by the RFC and the behaviour is as expected. If you set it to -1 (which is default now), it should work for you in the best possible way. I think that Nikita described it very well so I don't have anything to add...
 
PHP Copyright © 2001-2019 The PHP Group
All rights reserved.
Last updated: Sun Dec 15 23:01:27 2019 UTC