Commit cb8d9c3a authored by unknown's avatar unknown

Backport my_strntod() from 5.0

Change string->float conversion to delay division as long as possible.
This gives us more exact integer->float conversion for numbers of type '123.45E+02' (Bug #7740)




client/mysql.cc:
  Fix wront usage of charset (found during review of pushed code)
include/m_string.h:
  Backported my_strtod() from 5.0
mysql-test/mysql-test-run.sh:
  Run also mysql_client_test with --debug
mysql-test/r/ps_1general.result:
  Safety fix (if mysql_client_test.test fails)
mysql-test/r/type_float.result:
  More test
mysql-test/t/mysql_client_test.test:
  Comments for what to do if this test fails
mysql-test/t/ps_1general.test:
  Safety fix (if mysql_client_test.test fails)
mysql-test/t/type_float.test:
  More test to better test new strtod() function
  Test also bug #7740 (wrong comparsion between integer and float-in-integer-range)
sql/field.cc:
  Backport my_strntod() from 5.0
sql/item.cc:
  Backport my_strntod() from 5.0
sql/item.h:
  Backport my_strntod() from 5.0
sql/item_func.h:
  Backport my_strntod() from 5.0
sql/item_strfunc.cc:
  Backport my_strntod() from 5.0
sql/item_sum.cc:
  Backport my_strntod() from 5.0
sql/item_sum.h:
  Backport my_strntod() from 5.0
sql/procedure.h:
  Backport my_strntod() from 5.0
strings/ctype-simple.c:
  Backport my_strntod() from 5.0
strings/ctype-ucs2.c:
  Backport my_strntod() from 5.0
strings/strtod.c:
  Backport my_strntod() from 5.0
  Change conversion to delay division as long as possible.
  This gives us more exact integer-> float conversion for numbers of type '123.45E+02'
parent ae8c3e13
......@@ -2914,9 +2914,9 @@ com_status(String *buffer __attribute__((unused)),
MYSQL_ROW cur=mysql_fetch_row(result);
if (cur)
{
tee_fprintf(stdout, "Server characterset:\t%s\n", cur[0] ? cur[2] : "");
tee_fprintf(stdout, "Server characterset:\t%s\n", cur[2] ? cur[2] : "");
tee_fprintf(stdout, "Db characterset:\t%s\n", cur[3] ? cur[3] : "");
tee_fprintf(stdout, "Client characterset:\t%s\n", cur[2] ? cur[0] : "");
tee_fprintf(stdout, "Client characterset:\t%s\n", cur[0] ? cur[0] : "");
tee_fprintf(stdout, "Conn. characterset:\t%s\n", cur[1] ? cur[1] : "");
}
mysql_free_result(result);
......
......@@ -215,7 +215,7 @@ extern char *strstr(const char *, const char *);
extern int is_prefix(const char *, const char *);
/* Conversion routines */
double my_strtod(const char *str, char **end);
double my_strtod(const char *str, char **end, int *error);
double my_atof(const char *nptr);
extern char *llstr(longlong value,char *buff);
......
......@@ -444,6 +444,7 @@ while test $# -gt 0; do
--debug=d:t:A,$MYSQL_TEST_DIR/var/log/mysqldump.trace"
EXTRA_MYSQLBINLOG_OPT="$EXTRA_MYSQLBINLOG_OPT \
--debug=d:t:A,$MYSQL_TEST_DIR/var/log/mysqlbinlog.trace"
EXTRA_MYSQL_CLIENT_TEST_OPT="--debug=d:t:A,$MYSQL_TEST_DIR/var/log/mysql_client_test.trace"
;;
--fast)
FAST_START=1
......@@ -681,7 +682,7 @@ then
EXTRA_SLAVE_MYSQLD_OPT="$EXTRA_SLAVE_MYSQLD_OPT --user=root"
fi
MYSQL_CLIENT_TEST="$MYSQL_CLIENT_TEST --no-defaults --testcase --user=root --socket=$MASTER_MYSOCK --port=$MYSQL_TCP_PORT --silent"
MYSQL_CLIENT_TEST="$MYSQL_CLIENT_TEST --no-defaults --testcase --user=root --socket=$MASTER_MYSOCK --port=$MYSQL_TCP_PORT --silent $EXTRA_MYSQL_CLIENT_TEST_OPT"
MYSQL_DUMP="$MYSQL_DUMP --no-defaults -uroot --socket=$MASTER_MYSOCK --password=$DBPASSWD $EXTRA_MYSQLDUMP_OPT"
MYSQL_BINLOG="$MYSQL_BINLOG --no-defaults --local-load=$MYSQL_TMP_DIR $EXTRA_MYSQLBINLOG_OPT"
MYSQL_FIX_SYSTEM_TABLES="$MYSQL_FIX_SYSTEM_TABLES --no-defaults --host=localhost --port=$MASTER_MYPORT --socket=$MASTER_MYSOCK --user=root --password=$DBPASSWD --basedir=$BASEDIR --bindir=$CLIENT_BINDIR --verbose"
......
drop table if exists t5, t6, t7, t8;
drop database if exists mysqltest ;
drop database if exists client_test_db;
test_sequence
------ basic tests ------
drop table if exists t1, t9 ;
......
drop table if exists t1;
drop table if exists t1,t2;
SELECT 10,10.0,10.,.1e+2,100.0e-1;
10 10.0 10. .1e+2 100.0e-1
10 10.0 10 10 10
......@@ -8,6 +8,15 @@ SELECT 6e-05, -6e-05, --6e-05, -6e-05+1.000000;
SELECT 1e1,1.e1,1.0e1,1e+1,1.e+1,1.0e+1,1e-1,1.e-1,1.0e-1;
1e1 1.e1 1.0e1 1e+1 1.e+1 1.0e+1 1e-1 1.e-1 1.0e-1
10 10 10 10 10 10 0.1 0.1 0.1
SELECT 0.001e+1,0.001e-1, -0.001e+01,-0.001e-01;
0.001e+1 0.001e-1 -0.001e+01 -0.001e-01
0.01 0.0001 -0.01 -0.0001
SELECT 123.23E+02,-123.23E-02,"123.23E+02"+0.0,"-123.23E-02"+0.0;
123.23E+02 -123.23E-02 "123.23E+02"+0.0 "-123.23E-02"+0.0
12323 -1.2323 12323 -1.2323
SELECT 2147483647E+02,21474836.47E+06;
2147483647E+02 21474836.47E+06
214748364700 21474836470000
create table t1 (f1 float(24),f2 float(52));
show full columns from t1;
Field Type Collation Null Key Default Extra Privileges Comment
......@@ -139,6 +148,9 @@ create table t1 (c20 char);
insert into t1 values (5000.0);
Warnings:
Warning 1265 Data truncated for column 'c20' at row 1
insert into t1 values (0.5e4);
Warnings:
Warning 1265 Data truncated for column 'c20' at row 1
drop table t1;
create table t1 (f float(54));
ERROR 42000: Incorrect column specifier for column 'f'
......@@ -203,3 +215,23 @@ c
0.0002
2e-05
drop table t1;
CREATE TABLE t1 (
reckey int unsigned NOT NULL,
recdesc varchar(50) NOT NULL,
PRIMARY KEY (reckey)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
INSERT INTO t1 VALUES (108, 'Has 108 as key');
INSERT INTO t1 VALUES (109, 'Has 109 as key');
select * from t1 where reckey=108;
reckey recdesc
108 Has 108 as key
select * from t1 where reckey=1.08E2;
reckey recdesc
108 Has 108 as key
select * from t1 where reckey=109;
reckey recdesc
109 Has 109 as key
select * from t1 where reckey=1.09E2;
reckey recdesc
109 Has 109 as key
drop table t1;
# We run with different binaries for normal and --embedded-server
#
# If this test fails with "command "$MYSQL_CLIENT_TEST" failed",
# you should either run mysql_client_test separartely against a running
# server or run mysql-test-run --debug mysql_client_test and check
# var/log/mysql_client_test.trace
--disable_result_log
--exec echo $MYSQL_CLIENT_TEST
--exec $MYSQL_CLIENT_TEST
......@@ -11,6 +11,7 @@
--disable_warnings
drop table if exists t5, t6, t7, t8;
drop database if exists mysqltest ;
drop database if exists client_test_db;
--enable_warnings
--disable_query_log
......
......@@ -3,7 +3,7 @@
# Numeric floating point.
--disable_warnings
drop table if exists t1;
drop table if exists t1,t2;
--enable_warnings
--replace_result e-0 e- e+0 e+
......@@ -11,6 +11,9 @@ SELECT 10,10.0,10.,.1e+2,100.0e-1;
--replace_result e-00 e-0
SELECT 6e-05, -6e-05, --6e-05, -6e-05+1.000000;
SELECT 1e1,1.e1,1.0e1,1e+1,1.e+1,1.0e+1,1e-1,1.e-1,1.0e-1;
SELECT 0.001e+1,0.001e-1, -0.001e+01,-0.001e-01;
SELECT 123.23E+02,-123.23E-02,"123.23E+02"+0.0,"-123.23E-02"+0.0;
SELECT 2147483647E+02,21474836.47E+06;
create table t1 (f1 float(24),f2 float(52));
show full columns from t1;
......@@ -83,6 +86,7 @@ drop table t1;
#
create table t1 (c20 char);
insert into t1 values (5000.0);
insert into t1 values (0.5e4);
drop table t1;
# Errors
......@@ -120,3 +124,23 @@ create table t1 (c char(6));
insert into t1 values (2e5),(2e6),(2e-4),(2e-5);
select * from t1;
drop table t1;
#
# Test of comparison of integer with float-in-range (Bug #7840)
# This is needed because some ODBC applications (like Foxpro) uses
# floats for everything.
#
CREATE TABLE t1 (
reckey int unsigned NOT NULL,
recdesc varchar(50) NOT NULL,
PRIMARY KEY (reckey)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
INSERT INTO t1 VALUES (108, 'Has 108 as key');
INSERT INTO t1 VALUES (109, 'Has 109 as key');
select * from t1 where reckey=108;
select * from t1 where reckey=1.08E2;
select * from t1 where reckey=109;
select * from t1 where reckey=1.09E2;
drop table t1;
......@@ -968,7 +968,9 @@ int Field_decimal::store(longlong nr)
double Field_decimal::val_real(void)
{
int not_used;
return my_strntod(&my_charset_bin, ptr, field_length, NULL, &not_used);
char *end_not_used;
return my_strntod(&my_charset_bin, ptr, field_length, &end_not_used,
&not_used);
}
longlong Field_decimal::val_int(void)
......@@ -4360,8 +4362,9 @@ int Field_string::store(longlong nr)
double Field_string::val_real(void)
{
int not_used;
char *end_not_used;
CHARSET_INFO *cs=charset();
return my_strntod(cs,ptr,field_length,(char**)0,&not_used);
return my_strntod(cs, ptr, field_length, &end_not_used, &not_used);
}
......@@ -4577,7 +4580,9 @@ double Field_varstring::val_real(void)
int not_used;
uint length=uint2korr(ptr)+HA_KEY_BLOB_LENGTH;
CHARSET_INFO *cs=charset();
return my_strntod(cs, ptr+HA_KEY_BLOB_LENGTH, length, (char**)0, &not_used);
char *end_not_used;
return my_strntod(cs, ptr+HA_KEY_BLOB_LENGTH, length, &end_not_used,
&not_used);
}
......@@ -4955,12 +4960,13 @@ double Field_blob::val_real(void)
{
int not_used;
char *blob;
char *end_not_used;
memcpy_fixed(&blob,ptr+packlength,sizeof(char*));
if (!blob)
return 0.0;
uint32 length=get_length(ptr);
CHARSET_INFO *cs=charset();
return my_strntod(cs,blob,length,(char**)0, &not_used);
return my_strntod(cs,blob,length, &end_not_used, &not_used);
}
......
......@@ -1140,8 +1140,9 @@ double Item_param::val()
case LONG_DATA_VALUE:
{
int dummy_err;
char *end_not_used;
return my_strntod(str_value.charset(), (char*) str_value.ptr(),
str_value.length(), (char**) 0, &dummy_err);
str_value.length(), &end_not_used, &dummy_err);
}
case TIME_VALUE:
/*
......@@ -2585,9 +2586,11 @@ double Item_cache_str::val()
DBUG_ASSERT(fixed == 1);
int err;
if (value)
{
char *end_not_used;
return my_strntod(value->charset(), (char*) value->ptr(),
value->length(), (char**) 0, &err);
else
value->length(), &end_not_used, &err);
}
return (double)0;
}
......
......@@ -719,8 +719,9 @@ class Item_string :public Item
{
DBUG_ASSERT(fixed == 1);
int err;
char *end_not_used;
return my_strntod(str_value.charset(), (char*) str_value.ptr(),
str_value.length(), (char**) 0, &err);
str_value.length(), &end_not_used, &err);
}
longlong val_int()
{
......@@ -1044,9 +1045,10 @@ class Item_copy_string :public Item
double val()
{
int err;
char *end_not_used;
return (null_value ? 0.0 :
my_strntod(str_value.charset(), (char*) str_value.ptr(),
str_value.length(),NULL,&err));
str_value.length(), &end_not_used, &err));
}
longlong val_int()
{
......
......@@ -828,8 +828,11 @@ class Item_func_udf_str :public Item_udf_func
double val()
{
int err;
String *res; res=val_str(&str_value);
return res ? my_strntod(res->charset(),(char*) res->ptr(),res->length(),0,&err) : 0.0;
String *res;
char *end_not_used;
res=val_str(&str_value);
return res ? my_strntod(res->charset(), (char*) res->ptr(), res->length(),
&end_not_used, &err) : 0.0;
}
longlong val_int()
{
......
......@@ -63,10 +63,11 @@ double Item_str_func::val()
DBUG_ASSERT(fixed == 1);
int err;
char buff[64];
char *end_not_used;
String *res, tmp(buff,sizeof(buff), &my_charset_bin);
res= val_str(&tmp);
return res ? my_strntod(res->charset(), (char*) res->ptr(),res->length(),
NULL, &err) : 0.0;
&end_not_used, &err) : 0.0;
}
longlong Item_str_func::val_int()
......
......@@ -471,13 +471,14 @@ double Item_sum_hybrid::val()
{
DBUG_ASSERT(fixed == 1);
int err;
char *end_not_used;
if (null_value)
return 0.0;
switch (hybrid_type) {
case STRING_RESULT:
String *res; res=val_str(&str_value);
return (res ? my_strntod(res->charset(), (char*) res->ptr(),res->length(),
(char**) 0, &err) : 0.0);
&end_not_used, &err) : 0.0);
case INT_RESULT:
if (unsigned_flag)
return ulonglong2double(sum_int);
......
......@@ -600,9 +600,11 @@ class Item_sum_udf_str :public Item_udf_sum
double val()
{
int err;
String *res; res=val_str(&str_value);
char *end_not_used;
String *res;
res=val_str(&str_value);
return res ? my_strntod(res->charset(),(char*) res->ptr(),res->length(),
(char**) 0, &err) : 0.0;
&end_not_used, &err) : 0.0;
}
longlong val_int()
{
......
......@@ -59,7 +59,11 @@ class Item_proc_real :public Item_proc
void set(double nr) { value=nr; }
void set(longlong nr) { value=(double) nr; }
void set(const char *str,uint length,CHARSET_INFO *cs)
{ int err; value=my_strntod(cs,(char*) str,length,(char**)0,&err); }
{
int err;
char *end_not_used;
value= my_strntod(cs, (char*) str, length, &end_not_used, &err);
}
double val() { return value; }
longlong val_int() { return (longlong) value; }
String *val_str(String *s) { s->set(value,decimals,default_charset()); return s; }
......@@ -99,9 +103,10 @@ class Item_proc_string :public Item_proc
double val()
{
int err;
CHARSET_INFO *cs=str_value.charset();
CHARSET_INFO *cs= str_value.charset();
char *end_not_used;
return my_strntod(cs, (char*) str_value.ptr(), str_value.length(),
(char**) 0, &err);
&end_not_used, &err);
}
longlong val_int()
{
......
......@@ -773,31 +773,10 @@ double my_strntod_8bit(CHARSET_INFO *cs __attribute__((unused)),
char *str, uint length,
char **end, int *err)
{
char end_char;
double result;
errno= 0; /* Safety */
/*
The following define is to avoid warnings from valgrind as str[length]
may not be defined (which is not fatal in real life)
*/
#ifdef HAVE_purify
if (length == INT_MAX32)
#else
if (length == INT_MAX32 || str[length] == 0)
#endif
result= my_strtod(str, end);
else
{
end_char= str[length];
str[length]= 0;
result= my_strtod(str, end);
str[length]= end_char; /* Restore end char */
}
*err= errno;
return result;
length= 65535; /* Should be big enough */
*end= str + length;
return my_strtod(str, end, err);
}
......
......@@ -946,13 +946,10 @@ double my_strntod_ucs2(CHARSET_INFO *cs __attribute__((unused)),
break; /* Can't be part of double */
*b++= (char) wc;
}
*b= 0;
errno= 0;
res=my_strtod(buf, endptr);
*err= errno;
if (endptr)
*endptr=(char*) (*endptr-buf+nptr);
*endptr= b;
res= my_strtod(buf, endptr, err);
*endptr= nptr + (uint) (*endptr- buf);
return res;
}
......
......@@ -2,7 +2,7 @@
An alternative implementation of "strtod()" that is both
simplier, and thread-safe.
From mit-threads as bundled with MySQL 3.23
Original code from mit-threads as bundled with MySQL 3.23
SQL:2003 specifies a number as
......@@ -29,6 +29,8 @@
#include "my_base.h" /* Includes errno.h */
#include "m_ctype.h"
#define MAX_DBL_EXP 308
#define MAX_RESULT_FOR_MAX_EXP 1.79769313486232
static double scaler10[] = {
1.0, 1e10, 1e20, 1e30, 1e40, 1e50, 1e60, 1e70, 1e80, 1e90
};
......@@ -37,89 +39,157 @@ static double scaler1[] = {
};
double my_strtod(const char *str, char **end)
/*
Convert string to double (string doesn't have to be null terminated)
SYNOPSIS
my_strtod()
str String to convert
end_ptr Pointer to pointer that points to end of string
Will be updated to point to end of double.
error Will contain error number in case of error (else 0)
RETURN
value of str as double
*/
double my_strtod(const char *str, char **end_ptr, int *error)
{
double result= 0.0;
int negative, ndigits;
const char *old_str;
uint negative= 0, ndigits, dec_digits= 0, neg_exp= 0;
int exp= 0, digits_after_dec_point= 0;
const char *old_str, *end= *end_ptr, *start_of_number;
char next_char;
my_bool overflow=0;
*error= 0;
if (str >= end)
goto done;
while (my_isspace(&my_charset_latin1, *str))
str++;
{
if (++str == end)
goto done;
}
start_of_number= str;
if ((negative= (*str == '-')) || *str=='+')
str++;
{
if (++str == end)
goto done; /* Could be changed to error */
}
/* Skip pre-zero for easier calculation of overflows */
while (*str == '0')
{
if (++str == end)
goto done;
start_of_number= 0; /* Found digit */
}
old_str= str;
while (my_isdigit (&my_charset_latin1, *str))
while ((next_char= *str) >= '0' && next_char <= '9')
{
result= result*10.0 + (*str - '0');
str++;
result= result*10.0 + (next_char - '0');
if (++str == end)
{
next_char= 0; /* Found end of string */
break;
}
start_of_number= 0; /* Found digit */
}
ndigits= str-old_str;
ndigits= (uint) (str-old_str);
if (*str == '.')
if (next_char == '.' && str < end-1)
{
double p10=10;
str++;
old_str= str;
while (my_isdigit (&my_charset_latin1, *str))
/*
Continue to add numbers after decimal point to the result, as if there
was no decimal point. We will later (in the exponent handling) shift
the number down with the required number of fractions. We do it this
way to be able to get maximum precision for numbers like 123.45E+02,
which are normal for some ODBC applications.
*/
old_str= ++str;
while (my_isdigit(&my_charset_latin1, (next_char= *str)))
{
result= result*10.0 + (next_char - '0');
digits_after_dec_point++;
if (++str == end)
{
result+= (*str++ - '0')/p10;
p10*=10;
next_char= 0;
break;
}
ndigits+= str-old_str;
if (!ndigits) str--;
}
if (ndigits && (*str=='e' || *str=='E'))
/* If we found just '+.' or '.' then point at first character */
if (!(dec_digits= (uint) (str-old_str)) && start_of_number)
str= start_of_number; /* Point at '+' or '.' */
}
if ((next_char == 'e' || next_char == 'E') &&
dec_digits + ndigits != 0 && str < end-1)
{
int exp= 0;
int neg= 0;
const char *old_str= str++;
if ((neg= (*str == '-')) || *str == '+')
if ((neg_exp= (*str == '-')) || *str == '+')
str++;
if (!my_isdigit (&my_charset_latin1, *str))
if (str == end || !my_isdigit(&my_charset_latin1, *str))
str= old_str;
else
{
double scaler= 1.0;
while (my_isdigit (&my_charset_latin1, *str))
do
{
if (exp < 9999) /* protection against exp overflow */
exp= exp*10 + *str - '0';
if (exp < 9999) /* prot. against exp overfl. */
exp= exp*10 + (*str - '0');
str++;
} while (str < end && my_isdigit(&my_charset_latin1, *str));
}
}
if (exp >= 1000)
if ((exp= (neg_exp ? exp + digits_after_dec_point :
exp - digits_after_dec_point)))
{
if (neg)
double scaler;
if (exp < 0)
{
exp= -exp;
neg_exp= 1; /* neg_exp was 0 before */
}
if (exp + ndigits >= MAX_DBL_EXP + 1 && result)
{
/*
This is not 100 % as we actually will give an owerflow for
17E307 but not for 1.7E308 but lets cut some corners to make life
simpler
*/
if (exp + ndigits > MAX_DBL_EXP + 1 ||
result >= MAX_RESULT_FOR_MAX_EXP)
{
if (neg_exp)
result= 0.0;
else
overflow= 1;
goto done;
}
}
scaler= 1.0;
while (exp >= 100)
{
scaler*= 1.0e100;
exp-= 100;
}
scaler*= scaler10[exp/10]*scaler1[exp%10];
if (neg)
if (neg_exp)
result/= scaler;
else
result*= scaler;
}
}
done:
if (end)
*end = (char *)str;
*end_ptr= (char*) str; /* end of number */
if (overflow || isinf(result))
{
result= DBL_MAX;
errno= EOVERFLOW;
*error= EOVERFLOW;
}
return negative ? -result : result;
......@@ -127,6 +197,7 @@ double my_strtod(const char *str, char **end)
double my_atof(const char *nptr)
{
return (my_strtod(nptr, 0));
int error;
const char *end= nptr+65535; /* Should be enough */
return (my_strtod(nptr, (char**) &end, &error));
}
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