Commit 0b60184b authored by Alexey Kopytov's avatar Alexey Kopytov

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.
parent 510e9ddf
...@@ -1506,4 +1506,16 @@ DESC t6; ...@@ -1506,4 +1506,16 @@ DESC t6;
Field Type Null Key Default Extra Field Type Null Key Default Extra
NULL int(11) YES NULL NULL int(11) YES NULL
DROP TABLE t1, t2, t3, t4, t5, t6; 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 End of 5.0 tests
...@@ -1000,4 +1000,19 @@ SELECT * FROM (SELECT * FROM (SELECT NULL)a) b UNION SELECT a FROM t1; ...@@ -1000,4 +1000,19 @@ SELECT * FROM (SELECT * FROM (SELECT NULL)a) b UNION SELECT a FROM t1;
DESC t6; DESC t6;
DROP TABLE t1, t2, t3, t4, t5, 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 --echo End of 5.0 tests
...@@ -8587,16 +8587,16 @@ bool create_field::init(THD *thd, char *fld_name, enum_field_types fld_type, ...@@ -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) else if (tmp_length > PRECISION_FOR_FLOAT)
{ {
sql_type= FIELD_TYPE_DOUBLE; sql_type= FIELD_TYPE_DOUBLE;
length= DBL_DIG+7; /* -[digits].E+### */ length= MAX_DOUBLE_STR_LENGTH;
} }
else else
length= FLT_DIG+6; /* -[digits].E+## */ length= MAX_FLOAT_STR_LENGTH;
decimals= NOT_FIXED_DEC; decimals= NOT_FIXED_DEC;
break; break;
} }
if (!fld_length && !fld_decimals) if (!fld_length && !fld_decimals)
{ {
length= FLT_DIG+6; length= MAX_FLOAT_STR_LENGTH;
decimals= NOT_FIXED_DEC; decimals= NOT_FIXED_DEC;
} }
if (length < decimals && if (length < decimals &&
......
...@@ -6948,21 +6948,29 @@ bool Item_type_holder::join_types(THD *thd, Item *item) ...@@ -6948,21 +6948,29 @@ bool Item_type_holder::join_types(THD *thd, Item *item)
case REAL_RESULT: case REAL_RESULT:
{ {
if (decimals != NOT_FIXED_DEC) if (decimals != NOT_FIXED_DEC)
{
/*
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)
{ {
int delta1= max_length_orig - decimals_orig; int delta1= max_length_orig - decimals_orig;
int delta2= item->max_length - item->decimals; int delta2= item->max_length - item->decimals;
max_length= max(delta1, delta2) + decimals; max_length= max(delta1, delta2) + decimals;
if (fld_type == MYSQL_TYPE_FLOAT && max_length > FLT_DIG + 2) if (fld_type == MYSQL_TYPE_FLOAT && max_length > FLT_DIG + 2)
{ {
max_length= FLT_DIG + 6; max_length= MAX_FLOAT_STR_LENGTH;
decimals= NOT_FIXED_DEC; decimals= NOT_FIXED_DEC;
} }
if (fld_type == MYSQL_TYPE_DOUBLE && max_length > DBL_DIG + 2) else if (fld_type == MYSQL_TYPE_DOUBLE && max_length > DBL_DIG + 2)
{ {
max_length= DBL_DIG + 7; max_length= MAX_DOUBLE_STR_LENGTH;
decimals= NOT_FIXED_DEC; decimals= NOT_FIXED_DEC;
} }
} }
}
else else
max_length= (fld_type == MYSQL_TYPE_FLOAT) ? FLT_DIG+6 : DBL_DIG+7; max_length= (fld_type == MYSQL_TYPE_FLOAT) ? FLT_DIG+6 : DBL_DIG+7;
break; break;
......
...@@ -251,6 +251,11 @@ MY_LOCALE *my_locale_by_number(uint number); ...@@ -251,6 +251,11 @@ MY_LOCALE *my_locale_by_number(uint number);
#define PRECISION_FOR_DOUBLE 53 #define PRECISION_FOR_DOUBLE 53
#define PRECISION_FOR_FLOAT 24 #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 Default time to wait before aborting a new client connection
that does not respond to "initial server greeting" timely that does not respond to "initial server greeting" timely
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment