PHP7.1 json_encode() 浮动问题

PHP7.1 json_encode() Float Issue

这不是一个问题,因为它更像是一个注意事项。我将使用 json_encode() 的应用程序更新为 PHP7.1.1,我看到一个问题,即浮点数被更改为有时会扩展到 17 位数字。根据文档,PHP 7.1.x 在编码双精度值时开始使用 serialize_precision 而不是精度。我猜这导致了

的示例值

472.185

成为

472.18500000000006

在该值经过 json_encode() 之后。自从我发现后,我已经恢复到 PHP 7.0.16,我不再遇到 json_encode() 的问题。在恢复到 PHP 7.0.16.

之前,我还尝试更新到 PHP 7.1.2

这个问题背后的原因确实源于 PHP - Floating Number Precision,但是最终的所有原因都是因为 json_encode().[=23 中从精度到 serialize_precision 用法的变化=]

如果有人知道这个问题的解决方案,我会非常乐意聆听 reasoning/fix。

多维数组摘录(之前):

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

                    )

经过 json_encode()...

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

这让我有点抓狂,直到我终于找到 this bug which points you to this RFC 上面写着

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.

并且(强调我的)

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).

简而言之,有一种新方法可以让 PHP 7.1 json_encode 使用新的和改进的精度引擎。在 php.ini 中,您需要将 serialize_precision 更改为

serialize_precision = -1

您可以验证它是否适用于此命令行

php -r '$price = ["price" => round("45.99", 2)]; echo json_encode($price);'

你应该得到

{"price":45.99}

作为插件开发人员,我没有对服务器 php.ini 设置的一般访问权限。因此,根据 Machavity 的回答,我编写了一小段代码,您可以在 PHP 脚本中使用。只需将它放在脚本之上,json_encode 将照常工作。

if (version_compare(phpversion(), '7.1', '>=')) {
    ini_set( 'serialize_precision', -1 );
}

在某些情况下,需要多设置一个变量。我将其添加为第二个解决方案,因为我不确定第二个解决方案是否在第一个解决方案已被证明有效的所有情况下都能正常工作。

if (version_compare(phpversion(), '7.1', '>=')) {
    ini_set( 'precision', 17 );
    ini_set( 'serialize_precision', -1 );
}

我遇到了同样的问题,但只有serialize_precision = -1没有解决问题。我必须再做一步,将精度值从 14 更新为 17(因为它是在我的 PHP7.0 ini 文件中设置的)。显然,更改该数字的值会更改计算出的浮点数的值。

我正在对货币值进行编码,并且有诸如 330.46 编码为 330.4600000000000363797880709171295166015625 之类的东西。如果您不想或不能更改 PHP 设置并且您事先知道数据的结构,那么有一个非常简单的解决方案对我有用。只需将其转换为一个字符串(以下两者做同样的事情):

$data['discount'] = (string) $data['discount'];
$data['discount'] = '' . $data['discount'];

对于我的用例,这是一个快速有效的解决方案。请注意,这意味着当您从 JSON 解码回来时,它将是一个字符串,因为它将用双引号引起来。

您可以在 json_encode() 之前将 [max] => 472.185 从浮点数更改为字符串 ([max] => '472.185')。由于 json 无论如何都是一个字符串,因此在 json_encode() 之前将您的浮点值转换为字符串将保持您想要的值。

对我来说,问题是当 JSON_NUMERIC_CHECK 作为 json_encode () 的第二个参数被传递时,它将所有(不仅是整数)数字类型转换为 int。

我通过将精度和 serialize_precision 设置为相同的值 (10) 解决了这个问题:

ini_set('precision', 10);
ini_set('serialize_precision', 10);

您也可以在 php.ini

中进行设置

使用 number_format 将其存储为具有您需要的精确精度的字符串,然后 json_encode 使用 JSON_NUMERIC_CHECK 选项将其存储:

$foo = array('max' => number_format(472.185, 3, '.', ''));
print_r(json_encode($foo, JSON_NUMERIC_CHECK));

你得到:

{"max": 472.185}

请注意,这会将源对象中的所有数字字符串编码为结果 JSON 中的数字。

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

似乎问题发生在 serializeserialize_precision 设置为不同的值时。在我的例子中分别是 14 和 17。将它们都设置为 14 解决了这个问题,将 serialize_precision 设置为 -1 也是如此。

默认值 serialize_precision was changed to -1 as of PHP 7.1.0 表示“将使用增强的算法来舍入此类数字”。但是,如果您仍然遇到此问题,可能是因为您有一个来自先前版本的 PHP 配置文件。 (也许你在升级时保留了你的配置文件?)

另一件需要考虑的事情是,在您的情况下使用浮点值是否有意义。使用包含您的数字的字符串值来确保您的 JSON.

中始终保留正确的小数位数可能有意义也可能没有意义

在 php 7.2.32 上,解决方案是在 php.ini 中设置:

precision=10
serialize_precision=10

同样的问题。在 PHP 7.4 我尝试了不同的解决方案,但只有这个组合对我有用:

precision = 14
serialize_precision = 14