From 0b60184b90cfb7ed1eb927e5106eb194677d43f7 Mon Sep 17 00:00:00 2001 From: Alexey Kopytov Date: Fri, 27 Mar 2009 13:12:50 +0300 Subject: [PATCH] Fix for bug #43432: Union on floats does unnecessary rounding UNION could convert fixed-point FLOAT(M,D)/DOUBLE(M,D) columns to FLOAT/DOUBLE when aggregating data types from the SELECT substatements. While there is nothing particularly wrong with this behavior, especially when M is greater than the hardware precision limits, it could be confusing in cases when all SELECT statements in a union have the same FLOAT(M,D)/DOUBLE(M,D) columns with equal precision specifications listed in the same position. Since the manual is quite vague on what data type should be returned in such cases, the bug was fixed by implementing the most 'expected' behavior: do not convert FLOAT(M,D)/DOUBLE(M,D) to anything else if all SELECT statements in a UNION have the same precision for that column. mysql-test/r/union.result: Added a test case for bug #43432. mysql-test/t/union.test: Added a test case for bug #43432. sql/field.cc: Replaced FLT_DIG+6 and DBL_DIG+7 with a symbolic constant. sql/item.cc: Do not convert FLOAT(M,D)/DOUBLE(M,D) to anything else if all SELECT statements in a UNION have the same precision for that column. sql/mysql_priv.h: Added a symbolic constant for FLT_DIG+6 and DBL_DIG+7. --- mysql-test/r/union.result | 12 ++++++++++++ mysql-test/t/union.test | 15 +++++++++++++++ sql/field.cc | 6 +++--- sql/item.cc | 30 +++++++++++++++++++----------- sql/mysql_priv.h | 5 +++++ 5 files changed, 54 insertions(+), 14 deletions(-) diff --git a/mysql-test/r/union.result b/mysql-test/r/union.result index 6007fdd403a..3ae7953e283 100644 --- a/mysql-test/r/union.result +++ b/mysql-test/r/union.result @@ -1506,4 +1506,16 @@ DESC t6; Field Type Null Key Default Extra NULL int(11) YES NULL DROP TABLE t1, t2, t3, t4, t5, t6; +CREATE TABLE t1 (f FLOAT(9,6)); +CREATE TABLE t2 AS SELECT f FROM t1 UNION SELECT f FROM t1; +SHOW FIELDS FROM t2; +Field Type Null Key Default Extra +f float(9,6) YES NULL +DROP TABLE t1, t2; +CREATE TABLE t1(d DOUBLE(9,6)); +CREATE TABLE t2 AS SELECT d FROM t1 UNION SELECT d FROM t1; +SHOW FIELDS FROM t2; +Field Type Null Key Default Extra +d double(9,6) YES NULL +DROP TABLE t1, t2; End of 5.0 tests diff --git a/mysql-test/t/union.test b/mysql-test/t/union.test index 44f21abda19..35b9be60f2b 100644 --- a/mysql-test/t/union.test +++ b/mysql-test/t/union.test @@ -1000,4 +1000,19 @@ SELECT * FROM (SELECT * FROM (SELECT NULL)a) b UNION SELECT a FROM t1; DESC t6; DROP TABLE t1, t2, t3, t4, t5, t6; + +# +# Bug #43432: Union on floats does unnecessary rounding +# + +CREATE TABLE t1 (f FLOAT(9,6)); +CREATE TABLE t2 AS SELECT f FROM t1 UNION SELECT f FROM t1; +SHOW FIELDS FROM t2; +DROP TABLE t1, t2; + +CREATE TABLE t1(d DOUBLE(9,6)); +CREATE TABLE t2 AS SELECT d FROM t1 UNION SELECT d FROM t1; +SHOW FIELDS FROM t2; +DROP TABLE t1, t2; + --echo End of 5.0 tests diff --git a/sql/field.cc b/sql/field.cc index f8ab4b852ec..36cc4681dec 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -8587,16 +8587,16 @@ bool create_field::init(THD *thd, char *fld_name, enum_field_types fld_type, else if (tmp_length > PRECISION_FOR_FLOAT) { sql_type= FIELD_TYPE_DOUBLE; - length= DBL_DIG+7; /* -[digits].E+### */ + length= MAX_DOUBLE_STR_LENGTH; } else - length= FLT_DIG+6; /* -[digits].E+## */ + length= MAX_FLOAT_STR_LENGTH; decimals= NOT_FIXED_DEC; break; } if (!fld_length && !fld_decimals) { - length= FLT_DIG+6; + length= MAX_FLOAT_STR_LENGTH; decimals= NOT_FIXED_DEC; } if (length < decimals && diff --git a/sql/item.cc b/sql/item.cc index f32828629cf..f2b52a0f865 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -6949,18 +6949,26 @@ bool Item_type_holder::join_types(THD *thd, Item *item) { if (decimals != NOT_FIXED_DEC) { - int delta1= max_length_orig - decimals_orig; - int delta2= item->max_length - item->decimals; - max_length= max(delta1, delta2) + decimals; - if (fld_type == MYSQL_TYPE_FLOAT && max_length > FLT_DIG + 2) + /* + For FLOAT(M,D)/DOUBLE(M,D) do not change precision + if both fields have the same M and D + */ + if (item->max_length != max_length_orig || + item->decimals != decimals_orig) { - max_length= FLT_DIG + 6; - decimals= NOT_FIXED_DEC; - } - if (fld_type == MYSQL_TYPE_DOUBLE && max_length > DBL_DIG + 2) - { - max_length= DBL_DIG + 7; - decimals= NOT_FIXED_DEC; + int delta1= max_length_orig - decimals_orig; + int delta2= item->max_length - item->decimals; + max_length= max(delta1, delta2) + decimals; + if (fld_type == MYSQL_TYPE_FLOAT && max_length > FLT_DIG + 2) + { + max_length= MAX_FLOAT_STR_LENGTH; + decimals= NOT_FIXED_DEC; + } + else if (fld_type == MYSQL_TYPE_DOUBLE && max_length > DBL_DIG + 2) + { + max_length= MAX_DOUBLE_STR_LENGTH; + decimals= NOT_FIXED_DEC; + } } } else diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index b855af9a8d3..58ab7a08837 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -251,6 +251,11 @@ MY_LOCALE *my_locale_by_number(uint number); #define PRECISION_FOR_DOUBLE 53 #define PRECISION_FOR_FLOAT 24 +/* -[digits].E+## */ +#define MAX_FLOAT_STR_LENGTH (FLT_DIG + 6) +/* -[digits].E+### */ +#define MAX_DOUBLE_STR_LENGTH (DBL_DIG + 7) + /* Default time to wait before aborting a new client connection that does not respond to "initial server greeting" timely