diff options
Diffstat (limited to 'sql/my_decimal.cc')
-rw-r--r-- | sql/my_decimal.cc | 437 |
1 files changed, 437 insertions, 0 deletions
diff --git a/sql/my_decimal.cc b/sql/my_decimal.cc new file mode 100644 index 00000000..54b038cc --- /dev/null +++ b/sql/my_decimal.cc @@ -0,0 +1,437 @@ +/* + Copyright (c) 2005, 2010, Oracle and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ + +#include "mariadb.h" +#include "sql_priv.h" +#include <time.h> + +#ifndef MYSQL_CLIENT +#include "sql_class.h" // THD +#include "field.h" +#endif + +#define DIG_BASE 1000000000 +#define DIG_PER_DEC1 9 +#define ROUND_UP(X) (((X)+DIG_PER_DEC1-1)/DIG_PER_DEC1) + +#ifndef MYSQL_CLIENT +/** + report result of decimal operation. + + @param result decimal library return code (E_DEC_* see include/decimal.h) + + @todo + Fix error messages + + @return + result +*/ + +int decimal_operation_results(int result, const char *value, const char *type) +{ + /* Avoid calling current_thd on default path */ + if (likely(result == E_DEC_OK)) + return(result); + + THD *thd= current_thd; + switch (result) { + case E_DEC_TRUNCATED: + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_DATA_TRUNCATED, ER_THD(thd, ER_DATA_TRUNCATED), + value, type); + break; + case E_DEC_OVERFLOW: + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_DATA_OVERFLOW, ER_THD(thd, ER_DATA_OVERFLOW), + value, type); + break; + case E_DEC_DIV_ZERO: + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_DIVISION_BY_ZERO, ER_THD(thd, ER_DIVISION_BY_ZERO)); + break; + case E_DEC_BAD_NUM: + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_BAD_DATA, ER_THD(thd, ER_BAD_DATA), + value, type); + break; + case E_DEC_OOM: + my_error(ER_OUT_OF_RESOURCES, MYF(0)); + break; + default: + DBUG_ASSERT(0); + } + return result; +} + + +/** + @brief Converting decimal to string + + @details Convert given my_decimal to String; allocate buffer as needed. + + @param[in] mask what problems to warn on (mask of E_DEC_* values) + @param[in] d the decimal to print + @param[in] fixed_prec overall number of digits if ZEROFILL, 0 otherwise + @param[in] fixed_dec number of decimal places (if fixed_prec != 0) + @param[in] filler what char to pad with (ZEROFILL et al.) + @param[out] *str where to store the resulting string + + @return error coce + @retval E_DEC_OK + @retval E_DEC_TRUNCATED + @retval E_DEC_OVERFLOW + @retval E_DEC_OOM +*/ + +int my_decimal::to_string_native(String *str, uint fixed_prec, uint fixed_dec, + char filler, uint mask) const +{ + /* + Calculate the size of the string: For DECIMAL(a,b), fixed_prec==a + holds true iff the type is also ZEROFILL, which in turn implies + UNSIGNED. Hence the buffer for a ZEROFILLed value is the length + the user requested, plus one for a possible decimal point, plus + one if the user only wanted decimal places, but we force a leading + zero on them, plus one for the '\0' terminator. Because the type + is implicitly UNSIGNED, we do not need to reserve a character for + the sign. For all other cases, fixed_prec will be 0, and + my_decimal_string_length() will be called instead to calculate the + required size of the buffer. + */ + int length= (fixed_prec + ? (fixed_prec + ((fixed_prec == fixed_dec) ? 1 : 0) + 1) + : my_decimal_string_length(this)); + int result; + if (str->alloc(length+1)) // Alloc also space for \0 + return check_result(mask, E_DEC_OOM); + result= decimal2string(this, (char*) str->ptr(), + &length, (int)fixed_prec, fixed_dec, + filler); + str->length(length); + str->set_charset(&my_charset_numeric); + return check_result(mask, result); +} + + +/** + @brief Converting decimal to string with character set conversion + + @details Convert given my_decimal to String; allocate buffer as needed. + + @param[in] mask what problems to warn on (mask of E_DEC_* values) + @param[in] val the decimal to print + @param[in] fixed_prec overall number of digits if ZEROFILL, 0 otherwise + @param[in] fixed_dec number of decimal places (if fixed_prec != 0) + @param[in] filler what char to pad with (ZEROFILL et al.) + @param[out] *str where to store the resulting string + @param[in] cs character set + + @return error coce + @retval E_DEC_OK + @retval E_DEC_TRUNCATED + @retval E_DEC_OVERFLOW + @retval E_DEC_OOM + + Would be great to make it a method of the String class, + but this would need to include + my_decimal.h from sql_string.h and sql_string.cc, which is not desirable. +*/ +bool +str_set_decimal(uint mask, const my_decimal *val, + uint fixed_prec, uint fixed_dec, char filler, + String *str, CHARSET_INFO *cs) +{ + if (!(cs->state & MY_CS_NONASCII)) + { + // For ASCII-compatible character sets we can use to_string_native() + val->to_string_native(str, fixed_prec, fixed_dec, filler, mask); + str->set_charset(cs); + return FALSE; + } + else + { + /* + For ASCII-incompatible character sets (like UCS2) we + call my_string_native() on a temporary buffer first, + and then convert the result to the target character + with help of str->copy(). + */ + uint errors; + StringBuffer<DECIMAL_MAX_STR_LENGTH> tmp; + val->to_string_native(&tmp, fixed_prec, fixed_dec, filler, mask); + return str->copy(tmp.ptr(), tmp.length(), &my_charset_latin1, cs, &errors); + } +} + + +/* + Convert from decimal to binary representation + + SYNOPSIS + to_binary() + mask error processing mask + d number for conversion + bin pointer to buffer where to write result + prec overall number of decimal digits + scale number of decimal digits after decimal point + + NOTE + Before conversion we round number if it need but produce truncation + error in this case + + RETURN + E_DEC_OK + E_DEC_TRUNCATED + E_DEC_OVERFLOW +*/ + +int my_decimal::to_binary(uchar *bin, int prec, decimal_digits_t scale, + uint mask) const +{ + int err1= E_DEC_OK, err2; + my_decimal rounded; + my_decimal2decimal(this, &rounded); + rounded.frac= decimal_actual_fraction(&rounded); + if (scale < rounded.frac) + { + err1= E_DEC_TRUNCATED; + /* decimal_round can return only E_DEC_TRUNCATED */ + decimal_round(&rounded, &rounded, scale, HALF_UP); + } + err2= decimal2bin(&rounded, bin, prec, scale); + if (!err2) + err2= err1; + return check_result(mask, err2); +} + + +/* + Convert string for decimal when string can be in some multibyte charset + + SYNOPSIS + str2my_decimal() + mask error processing mask + from string to process + length length of given string + charset charset of given string + decimal_value buffer for result storing + + RESULT + E_DEC_OK + E_DEC_TRUNCATED + E_DEC_OVERFLOW + E_DEC_BAD_NUM + E_DEC_OOM +*/ + +int str2my_decimal(uint mask, const char *from, size_t length, + CHARSET_INFO *charset, my_decimal *decimal_value, + const char **end_ptr) +{ + int err; + if (charset->mbminlen > 1) + { + StringBuffer<STRING_BUFFER_USUAL_SIZE> tmp; + uint dummy_errors; + tmp.copy(from, length, charset, &my_charset_latin1, &dummy_errors); + char *end= (char*) tmp.end(); + err= string2decimal(tmp.ptr(), (decimal_t*) decimal_value, &end); + *end_ptr= from + charset->mbminlen * (size_t) (end - tmp.ptr()); + } + else + { + char *end= (char*) from + length; + err= string2decimal(from, (decimal_t*) decimal_value, &end); + *end_ptr= end; + } + check_result_and_overflow(mask, err, decimal_value); + return err; +} + + +/** + converts a decimal into a pair of integers - for integer and fractional parts + + special version, for decimals representing number of seconds. + integer part cannot be larger that 1e18 (otherwise it's an overflow). + fractional part is microseconds. +*/ +bool my_decimal2seconds(const my_decimal *d, ulonglong *sec, + ulong *microsec, ulong *nanosec) +{ + int pos; + + if (d->intg) + { + pos= (d->intg-1)/DIG_PER_DEC1; + *sec= d->buf[pos]; + if (pos > 0) + *sec+= static_cast<longlong>(d->buf[pos-1]) * DIG_BASE; + } + else + { + *sec=0; + pos= -1; + } + + *microsec= d->frac ? static_cast<longlong>(d->buf[pos+1]) / (DIG_BASE/1000000) : 0; + *nanosec= d->frac ? static_cast<longlong>(d->buf[pos+1]) % (DIG_BASE/1000000) : 0; + + if (pos > 1) + { + for (int i=0; i < pos-1; i++) + if (d->buf[i]) + { + *sec= LONGLONG_MAX; + break; + } + } + return d->sign(); +} + + +/** + converts a pair of integers (seconds, microseconds) into a decimal +*/ +my_decimal *seconds2my_decimal(bool sign, + ulonglong sec, ulong microsec, my_decimal *d) +{ + d->init(); + longlong2decimal(sec, d); // cannot fail + if (microsec) + { + d->buf[(d->intg-1) / DIG_PER_DEC1 + 1]= microsec * (DIG_BASE/1000000); + d->frac= 6; + } + ((decimal_t *)d)->sign= sign; + return d; +} + + +my_decimal *date2my_decimal(const MYSQL_TIME *ltime, my_decimal *dec) +{ + longlong date= (ltime->year*100L + ltime->month)*100L + ltime->day; + if (ltime->time_type > MYSQL_TIMESTAMP_DATE) + date= ((date*100L + ltime->hour)*100L+ ltime->minute)*100L + ltime->second; + return seconds2my_decimal(ltime->neg, date, ltime->second_part, dec); +} + + +void my_decimal_trim(ulonglong *precision, decimal_digits_t *scale) +{ + if (!(*precision) && !(*scale)) + { + *precision= 10; + *scale= 0; + return; + } +} + + +/* + Convert a decimal to an ulong with a descriptive error message +*/ + +int my_decimal2int(uint mask, const decimal_t *d, bool unsigned_flag, + longlong *l, decimal_round_mode round_type) +{ + int res; + my_decimal rounded; + /* decimal_round can return only E_DEC_TRUNCATED */ + decimal_round(d, &rounded, 0, round_type); + res= (unsigned_flag ? + decimal2ulonglong(&rounded, (ulonglong *) l) : + decimal2longlong(&rounded, l)); + if (res & mask) + { + char buff[DECIMAL_MAX_STR_LENGTH]; + int length= sizeof(buff); + decimal2string(d, buff, &length, 0, 0, 0); + + decimal_operation_results(res, buff, + unsigned_flag ? "UNSIGNED INT" : + "INT"); + } + return res; +} + + +longlong my_decimal::to_longlong(bool unsigned_flag) const +{ + longlong result; + my_decimal2int(E_DEC_FATAL_ERROR, this, unsigned_flag, &result); + return result; +} + + +my_decimal::my_decimal(Field *field) +{ + init(); + DBUG_ASSERT(!field->is_null()); +#ifdef DBUG_ASSERT_EXISTS + my_decimal *dec= +#endif + field->val_decimal(this); + DBUG_ASSERT(dec == this); +} + + +#ifndef DBUG_OFF +/* routines for debugging print */ + +/* print decimal */ +void +print_decimal(const my_decimal *dec) +{ + int i, end; + char buff[512], *pos; + pos= buff; + pos+= sprintf(buff, "Decimal: sign: %d intg: %d frac: %d { ", + dec->sign(), dec->intg, dec->frac); + end= ROUND_UP(dec->frac)+ROUND_UP(dec->intg)-1; + for (i=0; i < end; i++) + pos+= sprintf(pos, "%09d, ", dec->buf[i]); + pos+= sprintf(pos, "%09d }\n", dec->buf[i]); + fputs(buff, DBUG_FILE); +} + + +/* print decimal with its binary representation */ +void +print_decimal_buff(const my_decimal *dec, const uchar* ptr, int length) +{ + print_decimal(dec); + fprintf(DBUG_FILE, "Record: "); + for (int i= 0; i < length; i++) + { + fprintf(DBUG_FILE, "%02X ", (uint)((uchar *)ptr)[i]); + } + fprintf(DBUG_FILE, "\n"); +} + + +const char *dbug_decimal_as_string(char *buff, const my_decimal *val) +{ + int length= DECIMAL_MAX_STR_LENGTH + 1; /* minimum size for buff */ + if (!val) + return "NULL"; + (void)decimal2string((decimal_t*) val, buff, &length, 0,0,0); + return buff; +} + + +#endif /*DBUG_OFF*/ +#endif /*MYSQL_CLIENT*/ |