strtod.c 5.03 KB
Newer Older
serg@serg.mylan's avatar
serg@serg.mylan committed
1 2 3 4
/*
  An alternative implementation of "strtod()" that is both
  simplier, and thread-safe.

5
  Original code from mit-threads as bundled with MySQL 3.23
serg@serg.mylan's avatar
serg@serg.mylan committed
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

  SQL:2003 specifies a number as

<signed numeric literal> ::= [ <sign> ] <unsigned numeric literal>

<unsigned numeric literal> ::=
                    <exact numeric literal>
                  | <approximate numeric literal>

<exact numeric literal> ::=
                    <unsigned integer> [ <period> [ <unsigned integer> ] ]
                  | <period> <unsigned integer>

<approximate numeric literal> ::= <mantissa> E <exponent>

<mantissa> ::= <exact numeric literal>

<exponent> ::= <signed integer>

  So do we.

 */

29
#include "my_base.h"				/* Includes errno.h */
serg@serg.mylan's avatar
serg@serg.mylan committed
30 31
#include "m_ctype.h"

32 33
#define MAX_DBL_EXP	308
#define MAX_RESULT_FOR_MAX_EXP 1.79769313486232
serg@serg.mylan's avatar
serg@serg.mylan committed
34 35 36 37 38 39 40
static double scaler10[] = {
  1.0, 1e10, 1e20, 1e30, 1e40, 1e50, 1e60, 1e70, 1e80, 1e90
};
static double scaler1[] = {
  1.0, 10.0, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9
};

monty@mysql.com's avatar
monty@mysql.com committed
41

42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
/*
  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)
serg@serg.mylan's avatar
serg@serg.mylan committed
57 58
{
  double result= 0.0;
59 60 61 62
  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;
serg@serg.mylan's avatar
serg@serg.mylan committed
63
  my_bool overflow=0;
serg@serg.mylan's avatar
serg@serg.mylan committed
64

65 66 67 68
  *error= 0;
  if (str >= end)
    goto done;

serg@serg.mylan's avatar
serg@serg.mylan committed
69
  while (my_isspace(&my_charset_latin1, *str))
70 71 72 73
  {
    if (++str == end)
      goto done;
  }
serg@serg.mylan's avatar
serg@serg.mylan committed
74

75
  start_of_number= str;
serg@serg.mylan's avatar
serg@serg.mylan committed
76
  if ((negative= (*str == '-')) || *str=='+')
77 78 79 80 81 82 83 84 85 86 87 88
  {
    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 */
  }
serg@serg.mylan's avatar
serg@serg.mylan committed
89 90

  old_str= str;
91
  while ((next_char= *str) >= '0' && next_char <= '9')
serg@serg.mylan's avatar
serg@serg.mylan committed
92
  {
93 94 95 96 97 98 99
    result= result*10.0 + (next_char - '0');
    if (++str == end)
    {
      next_char= 0;                             /* Found end of string */
      break;
    }
    start_of_number= 0;                         /* Found digit */
serg@serg.mylan's avatar
serg@serg.mylan committed
100
  }
101
  ndigits= (uint) (str-old_str);
serg@serg.mylan's avatar
serg@serg.mylan committed
102

103
  if (next_char == '.' && str < end-1)
serg@serg.mylan's avatar
serg@serg.mylan committed
104
  {
105 106 107 108 109 110 111 112 113
    /*
      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)))
serg@serg.mylan's avatar
serg@serg.mylan committed
114
    {
115 116 117 118 119 120 121
      result= result*10.0 + (next_char - '0');
      digits_after_dec_point++;
      if (++str == end)
      {
        next_char= 0;
        break;
      }
serg@serg.mylan's avatar
serg@serg.mylan committed
122
    }
123 124 125
    /* 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 '.' */
serg@serg.mylan's avatar
serg@serg.mylan committed
126
  }
127 128
  if ((next_char == 'e' || next_char == 'E') &&
      dec_digits + ndigits != 0 && str < end-1)
serg@serg.mylan's avatar
serg@serg.mylan committed
129 130 131
  {
    const char *old_str= str++;

132
    if ((neg_exp= (*str == '-')) || *str == '+')
serg@serg.mylan's avatar
serg@serg.mylan committed
133 134
      str++;

135
    if (str == end || !my_isdigit(&my_charset_latin1, *str))
serg@serg.mylan's avatar
serg@serg.mylan committed
136 137 138
      str= old_str;
    else
    {
139
      do
serg@serg.mylan's avatar
serg@serg.mylan committed
140
      {
141 142
        if (exp < 9999)                         /* prot. against exp overfl. */
          exp= exp*10 + (*str - '0');
serg@serg.mylan's avatar
serg@serg.mylan committed
143
        str++;
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
      } while (str < end && my_isdigit(&my_charset_latin1, *str));
    }
  }
  if ((exp= (neg_exp ? exp + digits_after_dec_point :
             exp - digits_after_dec_point)))
  {
    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)
serg@serg.mylan's avatar
serg@serg.mylan committed
165
      {
166 167 168
        if (neg_exp)
          result= 0.0;
        else
monty@mysql.com's avatar
monty@mysql.com committed
169
          overflow= 1;
serg@serg.mylan's avatar
serg@serg.mylan committed
170 171 172
        goto done;
      }
    }
173 174 175 176 177 178 179 180 181 182 183
    scaler= 1.0;
    while (exp >= 100)
    {
      scaler*= 1.0e100;
      exp-= 100;
    }
    scaler*= scaler10[exp/10]*scaler1[exp%10];
    if (neg_exp)
      result/= scaler;
    else
      result*= scaler;
serg@serg.mylan's avatar
serg@serg.mylan committed
184 185 186
  }

done:
187
  *end_ptr= (char*) str;                        /* end of number */
serg@serg.mylan's avatar
serg@serg.mylan committed
188

monty@mysql.com's avatar
monty@mysql.com committed
189
  if (overflow || isinf(result))
serg@serg.mylan's avatar
serg@serg.mylan committed
190
  {
monty@mysql.com's avatar
monty@mysql.com committed
191
    result= DBL_MAX;
192
    *error= EOVERFLOW;
serg@serg.mylan's avatar
serg@serg.mylan committed
193 194
  }

serg@serg.mylan's avatar
serg@serg.mylan committed
195 196 197 198 199
  return negative ? -result : result;
}

double my_atof(const char *nptr)
{
200 201 202
  int error;
  const char *end= nptr+65535;                  /* Should be enough */
  return (my_strtod(nptr, (char**) &end, &error));
serg@serg.mylan's avatar
serg@serg.mylan committed
203
}