【问题标题】:PHP/MySQLi returning incorrect float values using prepared statementsPHP/MySQLi 使用准备好的语句返回不正确的浮点值
【发布时间】:2012-07-17 17:38:33
【问题描述】:

好的。我可能在这里很厚实(众所周知),但是我是否在 PHP 5.3.x 中的 MySQLi 中发现了准备好的语句的未记录“功能”,或者我错过了一些非常基本的东西。

短版 - 通过 MySQLi 和 mysqlnd 驱动程序在 PHP 中准备的语句返回不正确的浮点值

加长版

首先是一些测试数据

mysql> CREATE TABLE `t2` (`v` float(6,2) NOT NULL DEFAULT '0.00');
Query OK, 0 rows affected (0.08 sec)

mysql> insert into t2 (v) values (0.03);
Query OK, 1 row affected (0.00 sec)

通过准备好的语句检索上述值时会出现问题。当使用旧的 mysql_... 或标准 mysqli... 查询调用时,会返回正确的数据。但是,通过准备好的语句使用相同的查询会返回错误的值:

测试代码:

atom:~/testScripts> cat f1.php
<?php
require('passwds.php');

$q="select * from t2"; // same query for all

echo "/* Old style MySQL statements (deprecated) */".PHP_EOL;

$h1=mysql_connect(MYSQL_SERVER,MYSQL_USER,MYSQL_PASSWORD);
mysql_select_db('test',$h1);
$r1=mysql_query($q);
while ($f1=mysql_fetch_assoc($r1))
{
    echo print_r($f1,true).PHP_EOL;
}

echo "/* New style MySQLi statements */".PHP_EOL;

$h2=new mysqli(MYSQL_SERVER,MYSQL_USER,MYSQL_PASSWORD,'test');
$r2=$h2->query($q);
while ($f2=$r2->fetch_assoc())
{
    echo print_r($f2,true).PHP_EOL;
}

echo "/* New style MySQLi prepared statements */".PHP_EOL;

$h3=new mysqli(MYSQL_SERVER,MYSQL_USER,MYSQL_PASSWORD,'test');
$s3=$h3->stmt_init();
$s3->prepare($q);
$s3->execute(); // no binding required
$r3=$s3->get_result();
while ($f3=$r3->fetch_assoc())
{
    echo print_r($f3,true).PHP_EOL;
}

结果:

atom:~/testScripts> php -f f1.php
/* Old style MySQL statements (deprecated) */
Array
(
    [v] => 0.03
)

/* New style MySQLi statements */
Array
(
    [v] => 0.03
)

/* New style MySQLi prepared statements */
Array
(
    [v] => 0.029999999329448
)

如果我将表t2v 的类型更改为DOUBLE 而不是FLOAT,则准备好的语句返回的值是正确的。

在 Linux 机器(OpenSuse 12.1 32 位,MySQL 和 PHP 从源代码编译)和 Windows 7(64 位,从二进制文件安装 PHP,数据来自 Linux 机器)上运行脚本时,我得到相同的响应.我还在 OpenSuse 12.1 64 位安装(同样,使用 mysqlnd 驱动程序从源代码安装 PHP 和 MySQL)上进行了尝试,结果相同。所有版本都使用 PHP 提供的 mysqlnd 驱动程序

问题我是否错过了一些非常基本的东西,还是应该向 PHP.NET 报告错误?

抱歉,这是一个很长的问题,但我想我会提供尽可能多的数据

【问题讨论】:

  • 我的 0.02$ 是你错过了一些非常基本的东西——浮动的工作方式。浮点数不用于存储任何精确的东西。这就是一般计算的情况,而不仅仅是这里。
  • 我知道浮点数永远不会精确地存储在二进制中(我老了,做这个太久了)但我很困惑为什么我使用两个不同的查询系统得到不同的结果使用相同的 PHP SQL 驱动程序。不过,下面的 J Miller 却一针见血。谢谢你的评论。非常感谢

标签: php mysqli prepared-statement php-5.3


【解决方案1】:

实际上是旧的(前两个)陈述是错误的。浮点值本质上是不精确的,例如无法以浮点格式精确表示许多精确的小数。因此,最接近 0.03 的浮点值是 0.0299999...。您的前两个语句正在享受 MySQL 驱动程序的“好处”和/或 PHP 在数据类型转换期间为您舍入值。然而,MySQLi 准备好的语句使用二进制结果格式,将值“原样”直接转换为 PHP 数据类型,例如 int、float、string 等 (http://php.net/manual/en/mysqli.quickstart.prepared-statements.php)

对浮点精度问题进行更多研究(例如,http://dev.mysql.com/doc/refman/5.5/en/problems-with-float.html),看看它们是否真的符合您的需求。如果您想要精确的小数并且愿意牺牲一点存储空间和/或计算速度,请尝试 MySQL DECIMAL(X,Y) 类型。然而 MySQLi 可能仍会在内部将其转换为 PHP 浮点数/双精度数。

【讨论】:

  • 啊。那时我很傻。我现在不再想知道,现在开始使用 DOUBLE 值。感谢您为我清理。
  • 不,不傻 :) -- 只是隐藏在激动人心的文档中的细微差别之一。请注意,尽管双精度仍然是浮点格式,但它们只是双精度(8 对 4 字节)。因此,如果它显示带有双打的“.03”,那么在幕后仍然会进行某种自动舍入。最面向未来的方法是在显示/前端代码中处理自己的舍入。
猜你喜欢
  • 1970-01-01
  • 2012-01-31
  • 2016-07-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多