From f506f5cade2e403f185f545fb84a802e4570a4ab Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 28 May 2007 12:44:59 +0400 Subject: Fix for bug #28121 "INSERT or UPDATE into DOUBLE(200,0) field being truncated to 31 digits" When storing a large number to a FLOAT or DOUBLE field with fixed length, it could be incorrectly truncated if the field's length was greater than 31. This patch also does some code cleanups to be able to reuse code which is common between Field_float::store() and Field_double::store(). include/m_string.h: Added declarations for log_10 and log_01 from strtod.c mysql-test/r/type_float.result: Added the testcase for bug #28121 "INSERT or UPDATE into DOUBLE(200,0) field being truncated to 31 digits" mysql-test/t/type_float.test: Added the testcase for bug #28121 "INSERT or UPDATE into DOUBLE(200,0) field being truncated to 31 digits" sql/field.cc: Moved common code from Field_float::store() and Field_double:store() to Field_real::truncate() Fixed the algorithm to not truncate large input numbers if the field length is greater than 31. Fixed rounding to not depend on FLT_MAX/DBL_MAX constants. sql/field.h: Moved not_fixed member from Field_double to Field_real to allow code reuse between Field_float::store() and Field_double::store() Added truncate() method to Field_real which is used by both Field_float and Field_double sql/init.cc: log_10[] and log_01[] are now defined as statical arrays in strtod.c, no need to pre-computed them. sql/item_cmpfunc.cc: log_01[] now starts from 1e0, not from 1e-1 for consistency. sql/mysql_priv.h: Moved log_10[] and log_01[] from mysqld.cc to libmystrings. sql/mysqld.cc: Moved log_10[] and log_01[] from mysqld.cc to libmystrings. strings/strtod.c: Define and use log_10[] and log_01[] as static arrays of constants instead of values pre-computed at startup. --- sql/field.cc | 151 ++++++++++++++++++++++++----------------------------------- 1 file changed, 60 insertions(+), 91 deletions(-) (limited to 'sql/field.cc') diff --git a/sql/field.cc b/sql/field.cc index b2def4ca8d2..0178af86f43 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -3675,56 +3675,9 @@ int Field_float::store(const char *from,uint len,CHARSET_INFO *cs) int Field_float::store(double nr) { - float j; - int error= 0; + int error= truncate(&nr, FLT_MAX); + float j= nr; - if (isnan(nr)) - { - j= 0; - set_null(); - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); - error= 1; - } - else if (unsigned_flag && nr < 0) - { - j= 0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); - error= 1; - } - else - { - double max_value; - if (dec >= NOT_FIXED_DEC) - { - max_value= FLT_MAX; - } - else - { - uint tmp=min(field_length,array_elements(log_10)-1); - max_value= (log_10[tmp]-1)/log_10[dec]; - /* - The following comparison is needed to not get an overflow if nr - is close to FLT_MAX - */ - if (fabs(nr) < FLT_MAX/10.0e+32) - nr= floor(nr*log_10[dec]+0.5)/log_10[dec]; - } - if (nr < -max_value) - { - j= (float)-max_value; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); - error= 1; - } - else if (nr > max_value) - { - j= (float)max_value; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); - error= 1; - } - else - j= (float) nr; - } - #ifdef WORDS_BIGENDIAN if (table->s->db_low_byte_first) { @@ -3963,48 +3916,7 @@ int Field_double::store(const char *from,uint len,CHARSET_INFO *cs) int Field_double::store(double nr) { - int error= 0; - - if (isnan(nr)) - { - nr= 0; - set_null(); - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); - error= 1; - } - else if (unsigned_flag && nr < 0) - { - nr= 0; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); - error= 1; - } - else - { - double max_value; - if (not_fixed) - { - max_value= DBL_MAX; - } - else - { - uint tmp=min(field_length,array_elements(log_10)-1); - max_value= (log_10[tmp]-1)/log_10[dec]; - if (fabs(nr) < DBL_MAX/10.0e+32) - nr= floor(nr*log_10[dec]+0.5)/log_10[dec]; - } - if (nr < -max_value) - { - nr= -max_value; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); - error= 1; - } - else if (nr > max_value) - { - nr= max_value; - set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); - error= 1; - } - } + int error= truncate(&nr, DBL_MAX); #ifdef WORDS_BIGENDIAN if (table->s->db_low_byte_first) @@ -4023,6 +3935,63 @@ int Field_double::store(longlong nr, bool unsigned_val) return store(unsigned_val ? ulonglong2double((ulonglong) nr) : (double) nr); } +/* + If a field has fixed length, truncate the double argument pointed to by 'nr' + appropriately. + Also ensure that the argument is within [-max_value; max_value] range. +*/ + +int Field_real::truncate(double *nr, double max_value) +{ + int error= 1; + double res= *nr; + + if (isnan(res)) + { + res= 0; + set_null(); + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + goto end; + } + else if (unsigned_flag && res < 0) + { + res= 0; + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + goto end; + } + + if (!not_fixed) + { + uint order= field_length - dec; + uint step= array_elements(log_10) - 1; + max_value= 1.0; + for (; order > step; order-= step) + max_value*= log_10[step]; + max_value*= log_10[order]; + max_value-= 1.0 / log_10[dec]; + + double tmp= rint((res - floor(res)) * log_10[dec]) / log_10[dec]; + res= floor(res) + tmp; + } + + if (res < -max_value) + { + res= -max_value; + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + } + else if (res > max_value) + { + res= max_value; + set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1); + } + else + error= 0; + +end: + *nr= res; + return error; +} + int Field_real::store_decimal(const my_decimal *dm) { -- cgit v1.2.1