【问题标题】:PHP7.1 json_encode() Float IssuePHP7.1 json_encode() 浮动问题
【发布时间】:2017-08-16 07:46:34
【问题描述】:

这不是一个问题,因为它更多的是要注意。我将使用 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() 中从精度到serialize_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"
                }
            },

【问题讨论】:

  • ini_set('serialize_precision', 14); ini_set('precision', 14); 可能会让它像以前一样序列化,但是如果你真的依赖浮点数的特定精度,你就做错了。
  • “如果有人知道这个问题的解决方案”——什么问题?我在这里看不出任何问题。如果您使用 PHP 解码 JSON,您将返回您编码的值。如果您使用不同的语言对其进行解码,很可能会得到相同的值。无论哪种方式,如果您打印 12 位数字的值,您将返回原始(“正确”)值。您的应用程序使用的浮点数是否需要超过 12 位小数精度?
  • @axiac 472.185 != 472.18500000000006。前后有明显区别。这是对浏览器的 AJAX 请求的一部分,值需要保持原始状态。
  • 我试图避免使用字符串转换,因为最终产品是 Highcharts,它不会接受字符串。我认为如果您采用浮点值,将其转换为字符串,将其发送出去,然后让 javascript 使用 parseFloat() 将字符串解释回浮点数,我认为这是非常低效和草率的。不是吗?
  • @axiac 我注意到你是 PH​​P json_decode() 确实带回了原始浮点值。但是,当 javascript 将 JSON 字符串转换回对象时,它不会像您可能暗示的那样将值转换回 472.185 ......因此出现了问题。我会坚持我正在做的事情。

标签: php json precision php-7.1


【解决方案1】:

这让我有点抓狂,直到我终于找到了 this bug,它指向了 this RFC,上面写着

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

和(强调我的)

此 RFC 建议引入一个新设置 EG(precision)=-1 和 PG(serialize_precision)=-1,它使用 zend_dtoa() 的模式 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}

【讨论】:

  • G(precision)=-1PG(serialize_precision)=-1 也可以在 PHP 5.4 中使用
  • 小心serialize_precision = -1。使用 -1,此代码 echo json_encode([528.56 * 100]); 打印 [52855.99999999999]
  • @vl.lapikov 不过,这听起来更像是general floating point error。这里是a demo,你可以看得很清楚不仅仅是json_encode的问题
  • 所以我的代码在不同的机器上会有不同的表现,很好。这正是 PHP 享有盛誉的原因。
【解决方案2】:

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

【讨论】:

  • 请注意这一点,因为您的插件可能会更改开发人员应用程序其余部分的意外设置。但是,IMO,我不确定这个选项会有多大的破坏性......哈哈
  • 请注意,更改精度值(第二个示例)可能会对您在那里的其他数学运算产生更大的影响。 php.net/manual/en/ini.core.php#ini.precision
  • @RicardoMartins:根据文档,默认精度为 14。以上修复将其增加到 17。因此它应该更加精确。你同意吗?
  • @alev 我的意思是,只需更改 serialize_precision 就足够了,并且不要影响您的应用程序可能遇到的其他 PHP 行为
【解决方案3】:

我通过将精度和 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 解码回来时,它将是一个字符串,因为它会用双引号引起来。

【讨论】:

    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

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

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

      【讨论】:

      • 这不是和Alin Pop的回答基本一样吗?
      【解决方案7】:

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

      precision=10
      serialize_precision=10
      

      【讨论】:

        【解决方案8】:

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

        【讨论】:

        【解决方案9】:
        $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
        }
        

        【讨论】:

          【解决方案10】:

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

          【讨论】:

            【解决方案11】:

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

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

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

            【讨论】:

              【解决方案12】:

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

              【讨论】:

              • 这在一定程度上在技术上是正确的,但是效率很低。如果 JSON 字符串中的 Int/Float 没有被引用,那么 Javascript 可以将其视为实际的 Int/Float。执行您的再现会迫使您在浏览器端将每个值转换回 Int/Float 一次。在每个请求处理这个项目时,我经常处理 10000 多个值。最终会发生大量膨胀处理。
              • 如果您使用 JSON 向某处发送数据,并且需要一个数字但您发送的是一个字符串,则不能保证这能正常工作。在发送应用程序的开发人员无法控制接收应用程序的情况下,这不是解决方案。
              猜你喜欢
              • 1970-01-01
              • 2010-12-03
              • 1970-01-01
              • 2018-09-11
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多