Skip to content

Commit 8ac1ffd

Browse files
author
kaa@polly.local
committed
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().
1 parent 90468a7 commit 8ac1ffd

10 files changed

Lines changed: 199 additions & 134 deletions

File tree

include/m_string.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ extern char *stpcpy(char *, const char *); /* For AIX with gcc 2.95.3 */
105105
extern char NEAR _dig_vec_upper[];
106106
extern char NEAR _dig_vec_lower[];
107107

108+
/* Defined in strtod.c */
109+
extern const double log_10[310];
110+
extern const double log_01[310];
111+
108112
#ifdef BAD_STRING_COMPILER
109113
#define strmov(A,B) (memccpy(A,B,0,INT_MAX)-1)
110114
#else

mysql-test/r/type_float.result

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,3 +344,36 @@ create table t1 (s1 float(0,2));
344344
ERROR 42000: For float(M,D), double(M,D) or decimal(M,D), M must be >= D (column 's1').
345345
create table t1 (s1 float(1,2));
346346
ERROR 42000: For float(M,D), double(M,D) or decimal(M,D), M must be >= D (column 's1').
347+
create table t1 (f1 double(200, 0));
348+
insert into t1 values (1e199), (-1e199);
349+
insert into t1 values (1e200), (-1e200);
350+
insert into t1 values (2e200), (-2e200);
351+
Warnings:
352+
Warning 1264 Out of range value adjusted for column 'f1' at row 1
353+
Warning 1264 Out of range value adjusted for column 'f1' at row 2
354+
select f1 + 0e0 from t1;
355+
f1 + 0e0
356+
1e+199
357+
-1e+199
358+
1e+200
359+
-1e+200
360+
1e+200
361+
-1e+200
362+
drop table t1;
363+
create table t1 (f1 float(30, 0));
364+
insert into t1 values (1e29), (-1e29);
365+
insert into t1 values (1e30), (-1e30);
366+
insert into t1 values (2e30), (-2e30);
367+
Warnings:
368+
Warning 1264 Out of range value adjusted for column 'f1' at row 1
369+
Warning 1264 Out of range value adjusted for column 'f1' at row 2
370+
select f1 + 0e0 from t1;
371+
f1 + 0e0
372+
1.0000000150475e+29
373+
-1.0000000150475e+29
374+
1.0000000150475e+30
375+
-1.0000000150475e+30
376+
1.0000000150475e+30
377+
-1.0000000150475e+30
378+
drop table t1;
379+
End of 5.0 tests

mysql-test/t/type_float.test

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,23 @@ drop table t1;
222222
create table t1 (s1 float(0,2));
223223
--error 1427
224224
create table t1 (s1 float(1,2));
225+
226+
#
227+
# Bug #28121 "INSERT or UPDATE into DOUBLE(200,0) field being truncated to 31 digits"
228+
#
229+
230+
create table t1 (f1 double(200, 0));
231+
insert into t1 values (1e199), (-1e199);
232+
insert into t1 values (1e200), (-1e200);
233+
insert into t1 values (2e200), (-2e200);
234+
select f1 + 0e0 from t1;
235+
drop table t1;
236+
237+
create table t1 (f1 float(30, 0));
238+
insert into t1 values (1e29), (-1e29);
239+
insert into t1 values (1e30), (-1e30);
240+
insert into t1 values (2e30), (-2e30);
241+
select f1 + 0e0 from t1;
242+
drop table t1;
243+
244+
--echo End of 5.0 tests

sql/field.cc

Lines changed: 60 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -3675,56 +3675,9 @@ int Field_float::store(const char *from,uint len,CHARSET_INFO *cs)
36753675

36763676
int Field_float::store(double nr)
36773677
{
3678-
float j;
3679-
int error= 0;
3678+
int error= truncate(&nr, FLT_MAX);
3679+
float j= nr;
36803680

3681-
if (isnan(nr))
3682-
{
3683-
j= 0;
3684-
set_null();
3685-
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
3686-
error= 1;
3687-
}
3688-
else if (unsigned_flag && nr < 0)
3689-
{
3690-
j= 0;
3691-
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
3692-
error= 1;
3693-
}
3694-
else
3695-
{
3696-
double max_value;
3697-
if (dec >= NOT_FIXED_DEC)
3698-
{
3699-
max_value= FLT_MAX;
3700-
}
3701-
else
3702-
{
3703-
uint tmp=min(field_length,array_elements(log_10)-1);
3704-
max_value= (log_10[tmp]-1)/log_10[dec];
3705-
/*
3706-
The following comparison is needed to not get an overflow if nr
3707-
is close to FLT_MAX
3708-
*/
3709-
if (fabs(nr) < FLT_MAX/10.0e+32)
3710-
nr= floor(nr*log_10[dec]+0.5)/log_10[dec];
3711-
}
3712-
if (nr < -max_value)
3713-
{
3714-
j= (float)-max_value;
3715-
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
3716-
error= 1;
3717-
}
3718-
else if (nr > max_value)
3719-
{
3720-
j= (float)max_value;
3721-
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
3722-
error= 1;
3723-
}
3724-
else
3725-
j= (float) nr;
3726-
}
3727-
37283681
#ifdef WORDS_BIGENDIAN
37293682
if (table->s->db_low_byte_first)
37303683
{
@@ -3963,48 +3916,7 @@ int Field_double::store(const char *from,uint len,CHARSET_INFO *cs)
39633916

39643917
int Field_double::store(double nr)
39653918
{
3966-
int error= 0;
3967-
3968-
if (isnan(nr))
3969-
{
3970-
nr= 0;
3971-
set_null();
3972-
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
3973-
error= 1;
3974-
}
3975-
else if (unsigned_flag && nr < 0)
3976-
{
3977-
nr= 0;
3978-
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
3979-
error= 1;
3980-
}
3981-
else
3982-
{
3983-
double max_value;
3984-
if (not_fixed)
3985-
{
3986-
max_value= DBL_MAX;
3987-
}
3988-
else
3989-
{
3990-
uint tmp=min(field_length,array_elements(log_10)-1);
3991-
max_value= (log_10[tmp]-1)/log_10[dec];
3992-
if (fabs(nr) < DBL_MAX/10.0e+32)
3993-
nr= floor(nr*log_10[dec]+0.5)/log_10[dec];
3994-
}
3995-
if (nr < -max_value)
3996-
{
3997-
nr= -max_value;
3998-
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
3999-
error= 1;
4000-
}
4001-
else if (nr > max_value)
4002-
{
4003-
nr= max_value;
4004-
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
4005-
error= 1;
4006-
}
4007-
}
3919+
int error= truncate(&nr, DBL_MAX);
40083920

40093921
#ifdef WORDS_BIGENDIAN
40103922
if (table->s->db_low_byte_first)
@@ -4023,6 +3935,63 @@ int Field_double::store(longlong nr, bool unsigned_val)
40233935
return store(unsigned_val ? ulonglong2double((ulonglong) nr) : (double) nr);
40243936
}
40253937

3938+
/*
3939+
If a field has fixed length, truncate the double argument pointed to by 'nr'
3940+
appropriately.
3941+
Also ensure that the argument is within [-max_value; max_value] range.
3942+
*/
3943+
3944+
int Field_real::truncate(double *nr, double max_value)
3945+
{
3946+
int error= 1;
3947+
double res= *nr;
3948+
3949+
if (isnan(res))
3950+
{
3951+
res= 0;
3952+
set_null();
3953+
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
3954+
goto end;
3955+
}
3956+
else if (unsigned_flag && res < 0)
3957+
{
3958+
res= 0;
3959+
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
3960+
goto end;
3961+
}
3962+
3963+
if (!not_fixed)
3964+
{
3965+
uint order= field_length - dec;
3966+
uint step= array_elements(log_10) - 1;
3967+
max_value= 1.0;
3968+
for (; order > step; order-= step)
3969+
max_value*= log_10[step];
3970+
max_value*= log_10[order];
3971+
max_value-= 1.0 / log_10[dec];
3972+
3973+
double tmp= rint((res - floor(res)) * log_10[dec]) / log_10[dec];
3974+
res= floor(res) + tmp;
3975+
}
3976+
3977+
if (res < -max_value)
3978+
{
3979+
res= -max_value;
3980+
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
3981+
}
3982+
else if (res > max_value)
3983+
{
3984+
res= max_value;
3985+
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_OUT_OF_RANGE, 1);
3986+
}
3987+
else
3988+
error= 0;
3989+
3990+
end:
3991+
*nr= res;
3992+
return error;
3993+
}
3994+
40263995

40273996
int Field_real::store_decimal(const my_decimal *dm)
40283997
{

sql/field.h

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -453,19 +453,22 @@ class Field_longstr :public Field_str
453453
/* base class for float and double and decimal (old one) */
454454
class Field_real :public Field_num {
455455
public:
456+
my_bool not_fixed;
456457

457458
Field_real(char *ptr_arg, uint32 len_arg, uchar *null_ptr_arg,
458459
uchar null_bit_arg, utype unireg_check_arg,
459460
const char *field_name_arg,
460461
struct st_table *table_arg,
461462
uint8 dec_arg, bool zero_arg, bool unsigned_arg)
462463
:Field_num(ptr_arg, len_arg, null_ptr_arg, null_bit_arg, unireg_check_arg,
463-
field_name_arg, table_arg, dec_arg, zero_arg, unsigned_arg)
464+
field_name_arg, table_arg, dec_arg, zero_arg, unsigned_arg),
465+
not_fixed(dec_arg >= NOT_FIXED_DEC)
464466
{}
465467

466468

467469
int store_decimal(const my_decimal *);
468470
my_decimal *val_decimal(my_decimal *);
471+
int truncate(double *nr, double max_length);
469472
uint32 max_display_length() { return field_length; }
470473
};
471474

@@ -758,29 +761,25 @@ class Field_float :public Field_real {
758761

759762
class Field_double :public Field_real {
760763
public:
761-
my_bool not_fixed;
762764
Field_double(char *ptr_arg, uint32 len_arg, uchar *null_ptr_arg,
763765
uchar null_bit_arg,
764766
enum utype unireg_check_arg, const char *field_name_arg,
765767
struct st_table *table_arg,
766768
uint8 dec_arg,bool zero_arg,bool unsigned_arg)
767769
:Field_real(ptr_arg, len_arg, null_ptr_arg, null_bit_arg,
768770
unireg_check_arg, field_name_arg, table_arg,
769-
dec_arg, zero_arg, unsigned_arg),
770-
not_fixed(dec_arg >= NOT_FIXED_DEC)
771+
dec_arg, zero_arg, unsigned_arg)
771772
{}
772773
Field_double(uint32 len_arg, bool maybe_null_arg, const char *field_name_arg,
773774
struct st_table *table_arg, uint8 dec_arg)
774775
:Field_real((char*) 0, len_arg, maybe_null_arg ? (uchar*) "" : 0, (uint) 0,
775-
NONE, field_name_arg, table_arg, dec_arg, 0, 0),
776-
not_fixed(dec_arg >= NOT_FIXED_DEC)
776+
NONE, field_name_arg, table_arg, dec_arg, 0, 0)
777777
{}
778778
Field_double(uint32 len_arg, bool maybe_null_arg, const char *field_name_arg,
779-
struct st_table *table_arg, uint8 dec_arg, my_bool not_fixed_srg)
779+
struct st_table *table_arg, uint8 dec_arg, my_bool not_fixed_arg)
780780
:Field_real((char*) 0, len_arg, maybe_null_arg ? (uchar*) "" : 0, (uint) 0,
781-
NONE, field_name_arg, table_arg, dec_arg, 0, 0),
782-
not_fixed(not_fixed_srg)
783-
{}
781+
NONE, field_name_arg, table_arg, dec_arg, 0, 0)
782+
{not_fixed= not_fixed_arg; }
784783
enum_field_types type() const { return FIELD_TYPE_DOUBLE;}
785784
enum ha_base_keytype key_type() const { return HA_KEYTYPE_DOUBLE; }
786785
int store(const char *to,uint length,CHARSET_INFO *charset);

sql/init.cc

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121

2222
void unireg_init(ulong options)
2323
{
24-
uint i;
25-
double nr;
2624
DBUG_ENTER("unireg_init");
2725

2826
MYSYS_PROGRAM_DONT_USE_CURSES();
@@ -39,16 +37,5 @@ void unireg_init(ulong options)
3937

4038
VOID(strmov(reg_ext,".frm"));
4139
specialflag=SPECIAL_SAME_DB_NAME | options; /* Set options from argv */
42-
/* Make a tab of powers of 10 */
43-
for (i=0,nr=1.0; i < array_elements(log_10) ; i++)
44-
{ /* It's used by filesort... */
45-
log_10[i]= nr ; nr*= 10.0;
46-
}
47-
/* Make a tab of powers of 0.1 */
48-
for (i= 0, nr= 0.1; i < array_elements(log_01); i++)
49-
{
50-
log_01[i]= nr;
51-
nr*= 0.1;
52-
}
5340
DBUG_VOID_RETURN;
5441
}

sql/item_cmpfunc.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,7 @@ int Arg_comparator::set_compare_func(Item_bool_func2 *item, Item_result type)
504504
{
505505
if ((*a)->decimals < NOT_FIXED_DEC && (*b)->decimals < NOT_FIXED_DEC)
506506
{
507-
precision= 5 * log_01[max((*a)->decimals, (*b)->decimals)];
507+
precision= 5 * log_01[max((*a)->decimals, (*b)->decimals) + 1];
508508
if (func == &Arg_comparator::compare_real)
509509
func= &Arg_comparator::compare_real_fixed;
510510
else if (func == &Arg_comparator::compare_e_real)

sql/mysql_priv.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1262,8 +1262,6 @@ extern char language[FN_REFLEN], reg_ext[FN_EXTLEN];
12621262
extern char glob_hostname[FN_REFLEN], mysql_home[FN_REFLEN];
12631263
extern char pidfile_name[FN_REFLEN], system_time_zone[30], *opt_init_file;
12641264
extern char log_error_file[FN_REFLEN], *opt_tc_log_file;
1265-
extern double log_10[32];
1266-
extern double log_01[32];
12671265
extern ulonglong log_10_int[20];
12681266
extern ulonglong keybuff_size;
12691267
extern ulonglong thd_startup_options;

sql/mysqld.cc

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -434,8 +434,6 @@ ulong slow_launch_threads = 0, sync_binlog_period;
434434
ulong expire_logs_days = 0;
435435
ulong rpl_recovery_rank=0;
436436

437-
double log_10[32]; /* 10 potences */
438-
double log_01[32];
439437
time_t server_start_time;
440438

441439
char mysql_home[FN_REFLEN], pidfile_name[FN_REFLEN], system_time_zone[30];

0 commit comments

Comments
 (0)