PHP7.1 json_encode()浮点问题

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

472.185

成为

472.1850000000006

在那个值经过 json_encode()之后。自从我的发现,我已经回到 PHP 7.0.16,我不再有与 json_encode()的问题。在回到 PHP7.0.16之前,我还试图更新到 PHP7.1.2。

这个问题背后的原因确实来自于 浮点数精度,但是最终的原因是由于在 json_encode()中从精确使用到 Series _ Precision 使用的变化。

如果有人知道这个问题的解决方案,我将非常乐意倾听他们的推理/修正。

节选自多维数组(前) :

[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"
}
},
46360 次浏览

这让我有点抓狂,直到我终于找到了 这个窃听器指向你的 这个 RFC

目前 json_encode()使用 EG (精度) ,设置为14。这意味着最多使用14个数字来显示(打印)数字。IEEE 754双精度支持更高的精度和 serialize()/var_export()使用 PG (seralize _ Precision) ,设置为17是默认的,以便更精确。由于 json_encode()使用 EG (精度) ,因此即使 PHP 的 float 可以保存更精确的 float 值,json_encode()也会删除小数部分的较低位并破坏原始值。

还有(强调我的)

这个 RFC 建议引入一个新的设置 EG (精度) =-1和 使用 zend _ dtoa ()模式0的 PG (seralize _ Precision) =-1,该模式0使用更好的浮点数舍入算法(- 1用于表示0模式)

简而言之,有一种新的方法可以使 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 );
}

我也遇到了同样的问题,但是仅序列化 _ 精度 = -1并没有解决这个问题。我必须再执行一个步骤,将精度值从14更新到17(因为它是在我的 PHP7.0 ini 文件中设置的)。显然,更改该数字的值会更改计算的 float 的值。

我正在编码货币值,并有像 330.46编码到 330.4600000000000363797880709171295166015625的东西。如果您不想或者不能更改 PHP 设置,并且您事先知道数据的结构,那么有一个非常简单的解决方案对我来说是可行的。简单地将它强制转换为字符串(下面两者做同样的事情) :

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

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

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

对我来说,问题在于传递了作为 json _ encode ()的第二个参数的 JSON_NUMERIC_CHECK,它将所有(不仅仅是整数)数字类型转换为 int。

我解决这个问题的方法是,将住宅的精度和 seralize _ Precision 设置为相同的值(10) :

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

也可以在 php.ini 中设置这个值

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

$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 从 PHP 7.1.0开始改为 -1的默认值意味着“将使用一种四舍五入这些数字的增强算法”。但是如果您仍然遇到这个问题,可能是因为您有一个来自以前版本的 PHP 配置文件。(也许您在升级时保留了配置文件?)

另一件需要考虑的事情是,在您的情况下使用浮点值是否有意义。使用包含数字的字符串值来确保在 JSON 中始终保留适当的小数位数可能有意义,也可能没有意义。

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

precision=10
serialize_precision=10

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

precision = 14
serialize_precision = 14