2012-07-18 60 views
3

好的。我可能在這裏很沉悶(據瞭解),但是我在PHP 5.3.x的MySQLi中發現了一個沒有記錄的準備語句的「特性」,或者我錯過了一些非常基本的東西。PHP/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 
) 

如果我在表t2改變v類型的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

我的0.02美元是你錯過了一些非常基本的東西 - 漂浮的方式工作。浮游物不用於存儲任何精確的東西。這就是通常的計算方式,而不僅僅是這裏。 – eis 2012-07-18 19:07:01

+0

我知道浮點數永遠不會用二進制精確地存儲(我已經老了,並且這樣做的時間太長了),但我很困惑,爲什麼我使用兩個不同的查詢系統使用相同的PHP SQL驅動程序得到不同的結果。下面的J米勒已經擊中了頭部。儘管感謝您的評論。非常感謝 – DaveyBoy 2012-07-18 19:33:58

回答

2

它實際上是是錯誤的舊(前兩個)語句。浮點值本質上是不精確的,例如,沒有辦法精確地以浮點格式表示許多精確的小數。因此最接近.03的浮點值是.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 float/double。

+0

啊。那時我很愚蠢。我現在不再想知道了,現在開始使用DOUBLE值。感謝您爲我清理它。 – DaveyBoy 2012-07-18 19:35:17

+0

不,不是愚蠢的:) - 只是深藏在驚險文件中的那些細微差別之一。請注意,雖然雙打仍然是浮點格式,但它們只是雙精度(8對4字節)。所以如果它顯示「.03」雙打,那麼在幕後仍然會出現某種自動四捨五入。最有前途的方法是在顯示/前端代碼中處理四捨五入。 – 2012-07-18 19:58:27