com php-src: Patch for bug #67839 (mysqli does not handle 4-byte floats correctly): ext/mysqli/tests/bug67839.phpt ext/mysqlnd/config9.m4
ext/mysqlnd/mysqlnd_ps_codec.c

From: Date: Thu, 14 Aug 2014 18:19:56 +0000
Subject: com php-src: Patch for bug #67839 (mysqli does not handle 4-byte floats correctly): ext/mysqli/tests/bug67839.phpt ext/mysqlnd/config9.m4
ext/mysqlnd/mysqlnd_ps_codec.c
Groups: php.cvs 
Request: Send a blank email to php-cvs+get-80555@lists.php.net to get a copy of this message
Commit: c044164a96553668cc6c4ae1eb32f18219308851 Author: Keyur Govande <keyur_govande@yahoo.com> Thu, 14 Aug 2014 18:19:56 +0000 Parents: aeb633543bbd571d7fb440d54ae3b0f1a4dfece7 Branches: PHP-5.4 PHP-5..5 PHP-5.6 master Link: http://git.php.net/?p=php-src.git;a=commitdiff;h=c044164a96553668cc6c4ae1eb32f18219308851 Log: Patch for bug #67839 (mysqli does not handle 4-byte floats correctly) 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. Bugs: https://bugs.php.net/67839 Changed paths: A ext/mysqli/tests/bug67839.phpt M ext/mysqlnd/config9.m4 M ext/mysqlnd/mysqlnd_ps_codec.c Diff: diff --git a/ext/mysqli/tests/bug67839.phpt b/ext/mysqli/tests/bug67839.phpt new file mode 100644 index 0000000..b2821a2 --- /dev/null +++ b/ext/mysqli/tests/bug67839.phpt @@ -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 diff --git a/ext/mysqlnd/config9.m4 b/ext/mysqlnd/config9.m4 index 2c15c34..d730ba7 100644 --- a/ext/mysqlnd/config9.m4 +++ b/ext/mysqlnd/config9.m4 @@ -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 diff --git a/ext/mysqlnd/mysqlnd_ps_codec.c b/ext/mysqlnd/mysqlnd_ps_codec.c index e2640c7..3f7d310 100644 --- a/ext/mysqlnd/mysqlnd_ps_codec.c +++ b/ext/mysqlnd/mysqlnd_ps_codec.c @@ -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; } /* }}} */

« previous php.cvs (#80555) next »