PHP7.1 json_encode()浮动问题


98

这不是问题,因为更多的是要意识到。我更新了一个使用json_encode()PHP7.1.1的应用程序,然后看到一个问题,即浮点数被更改为有时会扩展到17位数字。根据文档,serialize_precision在对双精度值进行编码时,PHP 7.1.x开始使用而不是精度。我猜这引起了一个示例值

472.185

成为

472.18500000000006

那个价值过去了json_encode()。自发现以来,我已恢复为PHP 7.0.16,不再遇到的问题json_encode()。在还原到PHP 7.0.16之前,我还尝试了更新到PHP 7.1.2。

这个问题背后的原因确实来自PHP-浮点数精度,但是最终的所有原因都是因为从json_encode()

如果有人知道解决此问题的方法,我将非常乐于聆听推理/修复程序。

多维数组摘录(之前):

[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"
                }
            },

6
ini_set('serialize_precision', 14); ini_set('precision', 14);可能会使它像以前那样进行序列化,但是,如果您确实依赖浮动对象的特定精度,则说明您做错了什么。
apokryfos 17-3-23

1
“如果有人知道解决此问题的方法” –什么问题?我在这里看不到任何问题。如果使用PHP解码JSON,则会取回您编码的值。而且,如果您使用其他语言对其进行解码,则很有可能获得相同的值。无论哪种方式,如果您用12位数字打印该值,您都将获得原始(“正确”)值。您的应用程序所使用的浮点数是否需要超过12位十进制数字的精度?
axiac

12
@axiac 472.185!= 472.18500000000006。前后有一个明显的区别。这是向浏览器发出的AJAX请求的一部分,该值需要保持其原始状态。
Gwi7d31 '17

4
我试图避免使用字符串转换,因为最终产品是Highcharts,并且它将不接受字符串。我认为,如果您将float值转换为字符串,然后将其转换为字符串,然后将其发送出去,然后让javascript用parseFloat()将字符串解释为float,则效率会很低下。是不是
Gwi7d31 '17

1
@axiac我注意到您使用PHP json_decode()确实可以恢复原始的float值。但是,当javascript将JSON字符串转换回对象时,它不会像您可能暗示的那样将值转换回472.185,因此出现了问题。我会坚持下去。
Gwi7d31 '17

Answers:


101

这让我有点发疯,直到我终于找到此错误该错误将您指向此RFC,其中说

当前json_encode()使用设置为14的EG(精度)。这意味着最多使用14位数字来显示(打印)该数字。IEEE 754 double支持更高的精度,并且serialize()/var_export()使用默认设置为17的PG(serialize_precision)更加精确。由于json_encode()使用EG(precision),因此json_encode()即使PHP的float可以持有更精确的float值,也除去了小数部分并破坏了原始值。

和(强调我的)

该RFC建议引入新设置EG(precision)=-1和PG(serialize_precision)=-1,该设置使用zend_dtoa()的模式0,该模式使用更好的算法对舍入浮点数进行取整(-1用于表示0模式)

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

serialize_precision = -1

您可以验证它是否可以在此命令行下使用

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

你应该得到

{"price":45.99}

G(precision)=-1并且PG(serialize_precision)=-1 还可以在PHP 5.4中使用
kittygirl '18

1
请注意serialize_precision = -1。使用-1,此代码将echo json_encode([528.56 * 100]);打印出来[52855.99999999999]
vl.lapikov,

3
@ vl.lapikov听起来更像是一般的浮点错误。这是一个演示,您可以清楚地看到它不仅仅是一个json_encode问题
Machavity

41

作为插件开发人员,我没有服务器的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 );
}

3
请注意这一点,因为您的插件可能会为其他开发人员应用程序更改意外的设置。但是,海事组织,我不确定这个选择会带来多大的破坏性……大声笑
igorsantos07 '18年

请注意,更改精度值(第二个示例)可能会对您在那里进行的其他数学运算产生更大的影响。php.net/manual/en/ini.core.php#ini.precision
里卡多·马丁斯

@RicardoMartins:根据文档,默认精度为14。上面的修复将其提高到17。因此,它应该更加精确。你同意吗?
ALEV

@alev我的意思是,仅更改serialize_precision就足够了,并且不会损害您的应用程序可能会遇到的其他PHP行为
Ricardo Martins

6

我通过将precision和serialize_precision设置为相同的值(10)来解决此问题:

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

您也可以在php.ini中进行设置


4

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

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

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


3

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


3

其他解决方案对我不起作用。这是我在代码执行开始时必须添加的内容:

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

这基本上与Alin Pop的答案相同吗?
igorsantos07

1

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


1

使用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中的数字。


1
我已经在PHP 7.3中对此进行了测试,但是它不起作用(输出仍然具有很高的精度)。显然,自PHP 7.1起,JSON_NUMERIC_CHECK标志已损坏-php.net/manual/de/json.constants.php#123167
Philipp

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

0

serializeserialize_precision设置为不同的值时,似乎会出现问题。在我的情况下分别为14和17。将它们都设置为14可以解决此问题,而将其设置serialize_precision为-1也可以解决。

serialize_precision 自PHP 7.1.0起,默认值已更改为-1,这意味着“将使用四舍五入这种数字的增强算法”。但是,如果仍然遇到此问题,则可能是因为您有一个来自先前版本的PHP配置文件。(也许升级后保留了配置文件?)

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


-1

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


从技术上讲,在一定程度上是正确的,但效率很低。如果未引用JSON字符串中的Int / Float,则Javascript可以将其视为实际的Int / Float。执行再现会迫使您在浏览器端一次将每个单个值转换回Int / Float。每个请求处理该项目时,我经常处理10000+个值。许多膨胀处理将最终发生。
Gwi7d31 '19

如果您正在使用JSON在某处发送数据,并且应该有一个数字,但您发送的是字符串,则不能保证能正常工作。在发送应用程序的开发人员无法控制接收应用程序的情况下,这不是解决方案。
osullic
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.