/* Copyright (C) 2000 MySQL AB

   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; either version 2 of the License, or
   (at your option) any later version.

   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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

/*
  A quicker atof. About 2-10 times faster than standard atof on sparc.
  This don't handle iee-options (NaN...) and the presission :s is a little
  less for some high exponential numbers (+-1 at 14th place).
  Returns 0.0 if overflow or wrong number.
  Must be inited with init_my_atof to handle possibly overflows.
*/

#include <my_global.h>
#ifdef USE_MY_ATOF				/* Skipp if we don't want it */
#include <m_ctype.h>
#include <floatingpoint.h>
#include <signal.h>

/* Read a double. If float is wrong return 0.
   float ::= [space]* [sign] {digit}+  decimal-point {digit}+ [exponent] |
   [sign] {digit}+  [decimal-point {digit}*] exponent |
   [sign] {digit}+  decimal-point [{digit}*] exponent |
   [sign] decimal-point {digit}* exponent |
   exponent :: = exponent-marker [sign] {digit}+
   exponent-marker ::= E e
   */


#define is_exponent_marker(ch) (ch == 'E' || ch == 'e')

static void my_atof_overflow  _A((int sig,int code, struct sigcontext *scp,
			      char *addr));
static int parse_sign _A((char **str));
static void parse_float_number_part _A((char **str,double *number, int *length));
static void parse_decimal_number_part _A((char **str,double *number));
static int parse_int_number_part _A((char **str,uint *number));

static int volatile overflow,in_my_atof;
static sigfpe_handler_type old_overflow_handler;

void init_my_atof()
{
  old_overflow_handler = (sigfpe_handler_type)
    ieee_handler("get", "overflow", old_overflow_handler);
  VOID(ieee_handler("set", "overflow", my_atof_overflow));
  return;
}

static void my_atof_overflow(sig, code, scp, addr)
int sig;
int code;
struct sigcontext *scp;
char *addr;
{
  if (!in_my_atof)
    old_overflow_handler(sig,code,scp,addr);
  else
    overflow=1;
  return;
}

double my_atof(src)
const char *src;
{
  int		sign, exp_sign; /* is number negative (+1) or positive (-1) */
  int		length_before_point;
  double	after_point;	/* Number after decimal point and before exp */
  uint		exponent;	/* Exponent value */
  double	exp_log,exp_val;
  char		*tmp_src;
  double	result_number;

  tmp_src = (char*) src;
  while (isspace(tmp_src[0]))
    tmp_src++;					/* Skipp pre-space */
  sign = parse_sign(&tmp_src);
  overflow=0;
  in_my_atof=1;
  parse_float_number_part(&tmp_src, &result_number, &length_before_point);
  if (*tmp_src == '.')
  {
    tmp_src++;
    parse_decimal_number_part(&tmp_src, &after_point);
    result_number += after_point;
  }
  else if (length_before_point == 0)
  {
    in_my_atof=0;
    return 0.0;
  }
  if (is_exponent_marker(*tmp_src))
  {
    tmp_src++;
    exp_sign = parse_sign(&tmp_src);
    overflow|=parse_int_number_part(&tmp_src, &exponent);

    exp_log=10.0; exp_val=1.0;
    for (;;)
    {
      if (exponent & 1)
      {
	exp_val*= exp_log;
	exponent--;
      }
      if (!exponent)
	break;
      exp_log*=exp_log;
      exponent>>=1;
    }
    if (exp_sign < 0)
      result_number*=exp_val;
    else
      result_number/=exp_val;
  }
  if (sign > 0)
    result_number= -result_number;

  in_my_atof=0;
  if (overflow)
    return 0.0;
  return result_number;
}


static int parse_sign(str)
char **str;
{
  if (**str == '-')
  {
    (*str)++;
    return 1;
  }
  if (**str == '+')
    (*str)++;
  return -1;
}

	/* Get number with may be separated with ',' */

static void parse_float_number_part(str, number, length)
char **str;
double *number;
int *length;
{
  *number = 0;
  *length = 0;

  for (;;)
  {
    while (isdigit(**str))
    {
      (*length)++;
      *number = (*number * 10) + (**str - '0');
      (*str)++;
    }
    if (**str != ',')
      return;					/* Skipp possibly ',' */
    (*str)++;
  }
}

static void parse_decimal_number_part(str, number)
char **str;
double *number;
{
  double exp_log;

  *number = 0;
  exp_log=1/10.0;
  while (isdigit(**str))
  {
    *number+= (**str - '0')*exp_log;
    exp_log/=10;
    (*str)++;
  }
}

	/* Parses int suitably for exponent */

static int parse_int_number_part(str, number)
char **str;
uint *number;
{
  *number = 0;
  while (isdigit(**str))
  {
    if (*number >= ((uint) ~0)/10)
      return 1;						/* Don't overflow */
    *number = (*number * 10) + **str - '0';
    (*str)++;
  }
  return 0;
}

#endif
