PHP/MySQLi使用准备好的语句返回不正确的浮点值


PHP/MySQLi returning incorrect float values using prepared statements

好。我可能在这里很厚(这是已知的),但我是否在PHP5.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位安装上尝试过这一点(同样,PHP和MySQL是使用mysqlnd驱动程序从源代码安装的),结果相同。所有版本都使用PHP 附带的mysqlnd驱动程序

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

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

实际上是旧的(前两个)语句出错了。浮点值本质上是不精确的,例如,无法以浮点格式精确地表示许多精确的小数。因此,最接近.03的浮点值是.20299999……差不多。您的前两个语句享受到了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浮点/双精度。