]> granicus.if.org Git - php/commitdiff
Patch for bug #67839 (mysqli does not handle 4-byte floats correctly)
authorKeyur Govande <keyur_govande@yahoo.com>
Thu, 14 Aug 2014 18:19:56 +0000 (18:19 +0000)
committerKeyur Govande <keyur_govande@yahoo.com>
Thu, 14 Aug 2014 18:19:56 +0000 (18:19 +0000)
Before the patch, a value of 9.99 in a FLOAT column came out of mysqli
as 9.9998998641968. This is because it would naively cast a 4-byte float
into PHP's internal 8-byte double.
To fix this, with GCC we use the built-in decimal support to "up-convert"
the 4-byte float to a 8-byte double.
When that is not available, we fall back to converting the float
to a string and then converting the string to a double. This mimics
what MySQL does.

ext/mysqli/tests/bug67839.phpt [new file with mode: 0644]
ext/mysqlnd/config9.m4
ext/mysqlnd/mysqlnd_ps_codec.c

diff --git a/ext/mysqli/tests/bug67839.phpt b/ext/mysqli/tests/bug67839.phpt
new file mode 100644 (file)
index 0000000..b2821a2
--- /dev/null
@@ -0,0 +1,58 @@
+--TEST--
+mysqli_float_handling - ensure 4 byte float is handled correctly
+--SKIPIF--
+<?php
+       require_once('skipif.inc');
+       require_once('skipifemb.inc');
+       require_once('skipifconnectfailure.inc');
+?>
+--FILE--
+<?php
+       require('connect.inc');
+       if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) {
+               printf("[001] [%d] %s\n", mysqli_connect_errno(), mysqli_connect_error());
+               die();
+       }
+
+
+       if (!mysqli_query($link, "DROP TABLE IF EXISTS test")) {
+               printf("[002] [%d] %s\n", mysqli_errno($link), mysqli_error($link));
+               die();
+       }
+
+       if (!mysqli_query($link, "CREATE TABLE test(id INT PRIMARY KEY, fp4 FLOAT, fp8 DOUBLE) ENGINE = InnoDB")) {
+               printf("[003] [%d] %s\n", mysqli_errno($link), mysqli_error($link));
+               die();
+       }
+
+       // Insert via string to make sure the real floating number gets to the DB
+       if (!mysqli_query($link, "INSERT INTO test(id, fp4, fp8) VALUES (1, 9.9999, 9.9999)")) {
+               printf("[004] [%d] %s\n", mysqli_errno($link), mysqli_error($link));
+               die();
+       }
+
+       if (!($stmt = mysqli_prepare($link, "SELECT id, fp4, fp8 FROM test"))) {
+               printf("[005] [%d] %s\n", mysqli_errno($link), mysqli_error($link));
+               die();
+       }
+
+       if (!mysqli_stmt_execute($stmt)) {
+               printf("[006] [%d] %s\n", mysqli_errno($link), mysqli_error($link));
+               die();
+       }
+
+
+       if (!($result = mysqli_stmt_get_result($stmt))) {
+               printf("[007] [%d] %s\n", mysqli_errno($link), mysqli_error($link));
+               die();
+       }
+
+       $data = mysqli_fetch_assoc($result);
+       print $data['id'] . ": " . $data['fp4'] . ": " . $data['fp8'] . "\n";
+?>
+--CLEAN--
+<?php
+       require_once("clean_table.inc");
+?>
+--EXPECTF--
+1: 9.9999: 9.9999
index 2c15c34e8d39c44242f04dd696b011f445ed2853..d730ba7e71f06f54bf75d991be539fc76e05a4e9 100644 (file)
@@ -51,3 +51,29 @@ if test "$PHP_MYSQLND" != "no" || test "$PHP_MYSQLND_ENABLED" = "yes" || test "$
 #endif
   ])
 fi
+
+dnl
+dnl Check if the compiler supports Decimal32/64/128 types from the IEEE-754 2008 version
+dnl References: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1657.pdf
+dnl http://speleotrove.com/decimal/
+dnl
+AC_CACHE_CHECK([whether whether compiler supports Decimal32/64/128 types], ac_cv_decimal_fp_supported,[
+AC_TRY_RUN( [
+#include <stdio.h>
+
+int main(int argc, char **argv) {
+       typedef float dec32 __attribute__((mode(SD)));
+       dec32 k = 99.49f;
+       double d2 = (double)k;
+       return 0;
+}
+],[
+  ac_cv_decimal_fp_supported=yes
+],[
+  ac_cv_decimal_fp_supported=no
+],[
+  ac_cv_decimal_fp_supported=no
+])])
+if test "$ac_cv_decimal_fp_supported" = "yes"; then
+  AC_DEFINE(HAVE_DECIMAL_FP_SUPPORT, 1, [Define if the compiler supports Decimal32/64/128 types.])
+fi
index e2640c775baadb0361e46bb316b818bf1b2e8c04..3f7d31002f6d34da4b3b6a7e12082ade4ed2f443 100644 (file)
@@ -195,12 +195,53 @@ void ps_fetch_float(zval *zv, const MYSQLND_FIELD * const field,
                                        unsigned int pack_len, zend_uchar **row,
                                        zend_bool as_unicode TSRMLS_DC)
 {
-       float value;
+       float fval;
+       double dval;
        DBG_ENTER("ps_fetch_float");
-       float4get(value, *row);
-       ZVAL_DOUBLE(zv, value);
+       float4get(fval, *row);
        (*row)+= 4;
-       DBG_INF_FMT("value=%f", value);
+       DBG_INF_FMT("value=%f", fval);
+
+       /*
+        * The following is needed to correctly support 4-byte floats.
+        * Otherwise, a value of 9.99 in a FLOAT column comes out of mysqli
+        * as 9.9998998641968.
+        *
+        * For GCC, we use the built-in decimal support to "up-convert" a
+        * 4-byte float to a 8-byte double.
+        * When that is not available, we fall back to converting the float
+        * to a string and then converting the string to a double. This mimics
+        * what MySQL does.
+        */
+#ifdef HAVE_DECIMAL_FP_SUPPORT
+       {
+               typedef float dec32 __attribute__((mode(SD)));
+               dec32 d32val = fval;
+
+               /* The following cast is guaranteed to do the right thing */
+               dval = (double) d32val;
+       }
+#else
+       {
+               char num_buf[2048]; /* Over allocated */
+               char *s;
+
+               /* Convert to string. Ignoring localization, etc.
+                * Following MySQL's rules. If precision is undefined (NOT_FIXED_DEC i.e. 31)
+                * or larger than 31, the value is limited to 6 (FLT_DIG).
+                */
+               s = php_gcvt(fval,
+                            field->decimals >= 31 ? 6 : field->decimals,
+                            '.',
+                            'e',
+                            num_buf);
+
+               /* And now convert back to double */
+               dval = zend_strtod(s, NULL);
+       }
+#endif
+
+       ZVAL_DOUBLE(zv, dval);
        DBG_VOID_RETURN;
 }
 /* }}} */