summaryrefslogtreecommitdiff
path: root/sql
diff options
context:
space:
mode:
Diffstat (limited to 'sql')
-rw-r--r--sql/item_func.cc115
-rw-r--r--sql/item_func.h8
-rw-r--r--sql/item_strfunc.cc2
-rw-r--r--sql/mysql_priv.h3
4 files changed, 82 insertions, 46 deletions
diff --git a/sql/item_func.cc b/sql/item_func.cc
index e8ecd6b8f1c..b2bf9a27974 100644
--- a/sql/item_func.cc
+++ b/sql/item_func.cc
@@ -614,6 +614,14 @@ Item *Item_func::get_tmp_table_item(THD *thd)
return copy_or_same(thd);
}
+double Item_int_func::val_real()
+{
+ DBUG_ASSERT(fixed == 1);
+
+ return unsigned_flag ? (double) ((ulonglong) val_int()) : (double) val_int();
+}
+
+
String *Item_int_func::val_str(String *str)
{
DBUG_ASSERT(fixed == 1);
@@ -802,7 +810,10 @@ double Item_func_numhybrid::val_real()
return result;
}
case INT_RESULT:
- return (double)int_op();
+ {
+ longlong result= int_op();
+ return unsigned_flag ? (double) ((ulonglong) result) : (double) result;
+ }
case REAL_RESULT:
return real_op();
case STRING_RESULT:
@@ -1339,6 +1350,8 @@ longlong Item_func_mod::int_op()
DBUG_ASSERT(fixed == 1);
longlong value= args[0]->val_int();
longlong val2= args[1]->val_int();
+ longlong result;
+
if ((null_value= args[0]->null_value || args[1]->null_value))
return 0; /* purecov: inspected */
if (val2 == 0)
@@ -1348,9 +1361,13 @@ longlong Item_func_mod::int_op()
}
if (args[0]->unsigned_flag)
- return ((ulonglong) value) % val2;
+ result= args[1]->unsigned_flag ?
+ ((ulonglong) value) % ((ulonglong) val2) : ((ulonglong) value) % val2;
+ else
+ result= args[1]->unsigned_flag ?
+ value % ((ulonglong) val2) : value % val2;
- return value % val2;
+ return result;
}
double Item_func_mod::real_op()
@@ -1405,6 +1422,7 @@ void Item_func_mod::fix_length_and_dec()
{
Item_num_op::fix_length_and_dec();
maybe_null= 1;
+ unsigned_flag= args[0]->unsigned_flag;
}
@@ -1483,8 +1501,9 @@ double Item_func_abs::real_op()
longlong Item_func_abs::int_op()
{
longlong value= args[0]->val_int();
- null_value= args[0]->null_value;
- return value >= 0 ? value : -value;
+ if ((null_value= args[0]->null_value))
+ return 0;
+ return (value >= 0) || unsigned_flag ? value : -value;
}
@@ -1505,6 +1524,7 @@ my_decimal *Item_func_abs::decimal_op(my_decimal *decimal_value)
void Item_func_abs::fix_length_and_dec()
{
Item_func_num1::fix_length_and_dec();
+ unsigned_flag= args[0]->unsigned_flag;
}
@@ -1879,6 +1899,10 @@ my_decimal *Item_func_floor::decimal_op(my_decimal *decimal_value)
void Item_func_round::fix_length_and_dec()
{
+ int decimals_to_set;
+ longlong val1;
+ bool val1_unsigned;
+
unsigned_flag= args[0]->unsigned_flag;
if (!args[1]->const_item())
{
@@ -1887,8 +1911,14 @@ void Item_func_round::fix_length_and_dec()
hybrid_type= REAL_RESULT;
return;
}
-
- int decimals_to_set= max((int)args[1]->val_int(), 0);
+
+ val1= args[1]->val_int();
+ val1_unsigned= args[1]->unsigned_flag;
+ if (val1 < 0)
+ decimals_to_set= val1_unsigned ? INT_MAX : 0;
+ else
+ decimals_to_set= (val1 > INT_MAX) ? INT_MAX : (int) val1;
+
if (args[0]->decimals == NOT_FIXED_DEC)
{
max_length= args[0]->max_length;
@@ -1905,10 +1935,9 @@ void Item_func_round::fix_length_and_dec()
max_length= float_length(decimals);
break;
case INT_RESULT:
- if (!decimals_to_set &&
- (truncate || (args[0]->decimal_precision() < DECIMAL_LONGLONG_DIGITS)))
+ if ((!decimals_to_set && truncate) || (args[0]->decimal_precision() < DECIMAL_LONGLONG_DIGITS))
{
- int length_can_increase= test(!truncate && (args[1]->val_int() < 0));
+ int length_can_increase= test(!truncate && (val1 < 0) && !val1_unsigned);
max_length= args[0]->max_length + length_can_increase;
/* Here we can keep INT_RESULT */
hybrid_type= INT_RESULT;
@@ -1934,10 +1963,12 @@ void Item_func_round::fix_length_and_dec()
}
}
-double my_double_round(double value, int dec, bool truncate)
+double my_double_round(double value, longlong dec, bool dec_unsigned,
+ bool truncate)
{
double tmp;
- uint abs_dec= abs(dec);
+ bool dec_negative= (dec < 0) && !dec_unsigned;
+ ulonglong abs_dec= dec_negative ? -dec : dec;
/*
tmp2 is here to avoid return the value with 80 bit precision
This will fix that the test round(0.1,1) = round(0.1,1) is true
@@ -1947,7 +1978,11 @@ double my_double_round(double value, int dec, bool truncate)
tmp=(abs_dec < array_elements(log_10) ?
log_10[abs_dec] : pow(10.0,(double) abs_dec));
- if (truncate)
+ if (dec_negative && isinf(tmp))
+ tmp2= 0;
+ else if (!dec_negative && isinf(value * tmp))
+ tmp2= value;
+ else if (truncate)
{
if (value >= 0)
tmp2= dec < 0 ? floor(value/tmp)*tmp : floor(value*tmp)/tmp;
@@ -1963,24 +1998,35 @@ double my_double_round(double value, int dec, bool truncate)
double Item_func_round::real_op()
{
double value= args[0]->val_real();
- int dec= (int) args[1]->val_int();
if (!(null_value= args[0]->null_value || args[1]->null_value))
- return my_double_round(value, dec, truncate);
+ return my_double_round(value, args[1]->val_int(), args[1]->unsigned_flag,
+ truncate);
return 0.0;
}
+/*
+ Rounds a given value to a power of 10 specified as the 'to' argument,
+ avoiding overflows when the value is close to the ulonglong range boundary.
+*/
+
+static inline ulonglong my_unsigned_round(ulonglong value, ulonglong to)
+{
+ ulonglong tmp= value / to * to;
+ return (value - tmp < (to >> 1)) ? tmp : tmp + to;
+}
+
longlong Item_func_round::int_op()
{
longlong value= args[0]->val_int();
- int dec=(int) args[1]->val_int();
+ longlong dec= args[1]->val_int();
decimals= 0;
- uint abs_dec;
+ ulonglong abs_dec;
if ((null_value= args[0]->null_value || args[1]->null_value))
return 0;
- if (dec >= 0)
+ if ((dec >= 0) || args[1]->unsigned_flag)
return value; // integer have not digits after point
abs_dec= -dec;
@@ -1992,21 +2038,12 @@ longlong Item_func_round::int_op()
tmp= log_10_int[abs_dec];
if (truncate)
- {
- if (unsigned_flag)
- value= (ulonglong(value)/tmp)*tmp;
- else
- value= (value/tmp)*tmp;
- }
+ value= (unsigned_flag) ?
+ ((ulonglong) value / tmp) * tmp : (value / tmp) * tmp;
else
- {
- if (unsigned_flag)
- value= ((ulonglong(value)+(tmp>>1))/tmp)*tmp;
- else if ( value >= 0)
- value= ((value+(tmp>>1))/tmp)*tmp;
- else
- value= ((value-(tmp>>1))/tmp)*tmp;
- }
+ value= (unsigned_flag || value >= 0) ?
+ my_unsigned_round((ulonglong) value, tmp) :
+ -my_unsigned_round((ulonglong) -value, tmp);
return value;
}
@@ -2014,14 +2051,18 @@ longlong Item_func_round::int_op()
my_decimal *Item_func_round::decimal_op(my_decimal *decimal_value)
{
my_decimal val, *value= args[0]->val_decimal(&val);
- int dec=(int) args[1]->val_int();
- if (dec > 0)
+ longlong dec= args[1]->val_int();
+ if (dec > 0 || (dec < 0 && args[1]->unsigned_flag))
{
- decimals= min(dec, DECIMAL_MAX_SCALE); // to get correct output
+ dec= min((ulonglong) dec, DECIMAL_MAX_SCALE);
+ decimals= dec; // to get correct output
}
+ else if (dec < INT_MIN)
+ dec= INT_MIN;
+
if (!(null_value= (args[0]->null_value || args[1]->null_value ||
- my_decimal_round(E_DEC_FATAL_ERROR, value, dec, truncate,
- decimal_value) > 1)))
+ my_decimal_round(E_DEC_FATAL_ERROR, value, dec,
+ truncate, decimal_value) > 1)))
return decimal_value;
return 0;
}
diff --git a/sql/item_func.h b/sql/item_func.h
index 68591f9c6f5..237a173212e 100644
--- a/sql/item_func.h
+++ b/sql/item_func.h
@@ -278,7 +278,7 @@ public:
{ max_length= 21; }
Item_int_func(List<Item> &list) :Item_func(list) { max_length= 21; }
Item_int_func(THD *thd, Item_int_func *item) :Item_func(thd, item) {}
- double val_real() { DBUG_ASSERT(fixed == 1); return (double) val_int(); }
+ double val_real();
String *val_str(String*str);
enum Item_result result_type () const { return INT_RESULT; }
void fix_length_and_dec() {}
@@ -303,12 +303,6 @@ class Item_func_signed :public Item_int_func
public:
Item_func_signed(Item *a) :Item_int_func(a) {}
const char *func_name() const { return "cast_as_signed"; }
- double val_real()
- {
- double tmp= args[0]->val_real();
- null_value= args[0]->null_value;
- return tmp;
- }
longlong val_int();
longlong val_int_from_str(int *error);
void fix_length_and_dec()
diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc
index 6b1921e5bc8..2bf196e9989 100644
--- a/sql/item_strfunc.cc
+++ b/sql/item_strfunc.cc
@@ -1918,7 +1918,7 @@ String *Item_func_format::val_str(String *str)
double nr= args[0]->val_real();
if ((null_value=args[0]->null_value))
return 0; /* purecov: inspected */
- nr= my_double_round(nr, decimals, FALSE);
+ nr= my_double_round(nr, (longlong) decimals, FALSE, FALSE);
/* Here default_charset() is right as this is not an automatic conversion */
str->set(nr,decimals, default_charset());
if (isnan(nr))
diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h
index 88bd8576965..ca3e0f1ee7f 100644
--- a/sql/mysql_priv.h
+++ b/sql/mysql_priv.h
@@ -1537,7 +1537,8 @@ ha_rows filesort(THD *thd, TABLE *form,struct st_sort_field *sortorder,
ha_rows max_rows, ha_rows *examined_rows);
void filesort_free_buffers(TABLE *table, bool full);
void change_double_for_sort(double nr,byte *to);
-double my_double_round(double value, int dec, bool truncate);
+double my_double_round(double value, longlong dec, bool dec_unsigned,
+ bool truncate);
int get_quick_record(SQL_SELECT *select);
int calc_weekday(long daynr,bool sunday_first_day_of_week);
uint calc_week(TIME *l_time, uint week_behaviour, uint *year);