/*-------------------------------------------------------------------------
 *
 * numutils.c--
 *    utility functions for I/O of built-in numeric types.
 *
 *	integer:		itoa, ltoa
 *	floating point:		ftoa, atof1
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *    $Header: /cvsroot/pgsql/src/backend/utils/adt/numutils.c,v 1.11 1997/08/12 20:16:02 momjian Exp $
 *
 *-------------------------------------------------------------------------
 */
#include <stdio.h>		/* for sprintf() */
#include <errno.h>
#include <math.h>
#include "postgres.h"
#include "utils/builtins.h"		/* where the declarations go */
#ifndef HAVE_MEMMOVE
# include <regex/utils.h>
#else
# include <string.h>
#endif
#include <port-protos.h> /* ecvt(), fcvt() */

int32
pg_atoi(char *s, int size, int c)
{
    long l;
    char *badp = (char *) NULL;
    
    Assert(s);
    
    errno = 0;
    l = strtol(s, &badp, 10);
    if (errno)		/* strtol must set ERANGE */
	elog(WARN, "pg_atoi: error reading \"%s\": %m", s);
    if (badp && *badp && (*badp != c))
	elog(WARN, "pg_atoi: error in \"%s\": can\'t parse \"%s\"", s, badp);
    
    switch (size) {
    case sizeof(int32):
#ifdef HAS_LONG_LONG
	/* won't get ERANGE on these with 64-bit longs... */
	if (l < -0x80000000L) {
	    errno = ERANGE;
	    elog(WARN, "pg_atoi: error reading \"%s\": %m", s);
	}
	if (l > 0x7fffffffL) {
	    errno = ERANGE;
	    elog(WARN, "pg_atoi: error reading \"%s\": %m", s);
	}
#endif /* HAS_LONG_LONG */
	break;
    case sizeof(int16):
	if (l < -0x8000) {
	    errno = ERANGE;
	    elog(WARN, "pg_atoi: error reading \"%s\": %m", s);
	}
	if (l > 0x7fff) {
	    errno = ERANGE;
	    elog(WARN, "pg_atoi: error reading \"%s\": %m", s);
	}
	break;
    case sizeof(int8):
	if (l < -0x80) {
	    errno = ERANGE;
	    elog(WARN, "pg_atoi: error reading \"%s\": %m", s);
	}
	if (l > 0x7f) {
	    errno = ERANGE;
	    elog(WARN, "pg_atoi: error reading \"%s\": %m", s);
	}
	break;
    default:
	elog(WARN, "pg_atoi: invalid result size: %d", size);
    }
    return((int32) l);
}

/*
 *	itoa		- converts a short int to its string represention
 *
 *	Note:
 *		previously based on ~ingres/source/gutil/atoi.c
 *		now uses vendor's sprintf conversion
 */
void
itoa(int i, char *a)
{
    sprintf(a, "%hd", (short)i);
}

/*
 *	ltoa		- converts a long int to its string represention
 *
 *	Note:
 *		previously based on ~ingres/source/gutil/atoi.c
 *		now uses vendor's sprintf conversion
 */
void
ltoa(int32 l, char *a)
{
    sprintf(a, "%d", l);
}

/*
 **  ftoa	- FLOATING POINT TO ASCII CONVERSION
 **
 **	CODE derived from ingres, ~ingres/source/gutil/ftoa.c
 **
 **	'Value' is converted to an ascii character string and stored
 **	into 'ascii'.  Ascii should have room for at least 'width' + 1
 **	characters.  'Width' is the width of the output field (max).
 **	'Prec' is the number of characters to put after the decimal
 **	point.  The format of the output string is controlled by
 **	'format'.
 **
 **	'Format' can be:
 **		e or E: "E" format output
 **		f or F:  "F" format output
 **		g or G:  "F" format output if it will fit, otherwise
 **			use "E" format.
 **		n or N:  same as G, but decimal points will not always
 **			be aligned.
 **
 **	If 'format' is upper case, the "E" comes out in upper case;
 **	otherwise it comes out in lower case.
 **
 **	When the field width is not big enough, it fills the field with
 **	stars ("*****") and returns zero.  Normal return is the width
 **	of the output field (sometimes shorter than 'width').
 */
int
ftoa(double value, char *ascii, int width, int prec1, char format)
{
#ifndef HAVE_FCVT
	char	out[256];
	char	fmt[256];
	int	ret;

	(void) sprintf(fmt, "%%%d.%d%c", width, prec1, format);
	(void) sprintf(out, fmt, value);
	if ((ret = strlen(out)) > width) {
		memset(ascii, '*', width - 2);
		ascii[width] = 0;
		return(0);
	}
	strcpy(ascii, out);
	return(ret);
#else
    auto int	expon;
    auto int	sign;
    register int	avail = 0;
    register char	*a = NULL;
    register char	*p = NULL;
    char		mode;
    int		lowercase;
    int		prec;
/*    extern char	*ecvt(), *fcvt();*/
    
    prec = prec1;
    mode = format;
    lowercase = 'a' - 'A';
    if (mode >= 'a')
	mode -= 'a' - 'A';
    else
	lowercase = 0;
    
    if (mode != 'E') {
	/* try 'F' style output */
	p = fcvt(value, prec, &expon, &sign);
	avail = width;
	a = ascii;
	
	/* output sign */
	if (sign) {
	    avail--;
	    *a++ = '-';
	}
	
	/* output '0' before the decimal point */
	if (expon <= 0) {
	    *a++ = '0';
	    avail--;
	}
	
	/* compute space length left after dec pt and fraction */
	avail -= prec + 1;
	if (mode == 'G')
	    avail -= 4;
	
	if (avail >= expon) {
	    
	    /* it fits.  output */
	    while (expon > 0) {
		/* output left of dp */
		expon--;
		if (*p) {
		    *a++ = *p++;
		} else
		    *a++ = '0';
	    }
	    
	    /* output fraction (right of dec pt) */
	    avail = expon;
	    goto frac_out;
	}
	/* won't fit; let's hope for G format */
    }
    
    if (mode != 'F') {
	/* try to do E style output */
	p = ecvt(value, prec + 1, &expon, &sign);
	avail = width - 5;
	a = ascii;
	
	/* output the sign */
	if (sign) {
	    *a++ = '-';
	    avail--;
	}
    }
    
    /* check for field too small */
    if (mode == 'F' || avail < prec) {
	/* sorry joker, you lose */
	a = ascii;
	for (avail = width; avail > 0; avail--)
	    *a++ = '*';
	*a = 0;
	return (0);
    }
    
    /* it fits; output the number */
    mode = 'E';
    
    /* output the LHS single digit */
    *a++ = *p++;
    expon--;
    
    /* output the rhs */
    avail = 1;
    
 frac_out:
    *a++ = '.';
    while (prec > 0) {
	prec--;
	if (avail < 0) {
	    avail++;
	    *a++ = '0';
	} else {
	    if (*p)
		*a++ = *p++;
	    else
		*a++ = '0';
	}
    }
    
    /* output the exponent */
    if (mode == 'E') {
	*a++ = 'E' + lowercase;
	if (expon < 0) {
	    *a++ = '-';
	    expon = -expon;
	} else
	    *a++ = '+';
	*a++ = (expon / 10) % 10 + '0';
	*a++ = expon % 10 + '0';
    }
    
    /* output spaces on the end in G format */
    if (mode == 'G') {
	*a++ = ' ';
	*a++ = ' ';
	*a++ = ' ';
	*a++ = ' ';
    }
    
    /* finally, we can return */
    *a = 0;
    avail = a - ascii;
    return (avail);
#endif /* !BSD44_derived */
}

/*
 **   atof1	- ASCII TO FLOATING CONVERSION
 **
 **	CODE derived from ~ingres/source/gutil/atof.c
 **
 **	Converts the string 'str' to floating point and stores the
 **	result into the cell pointed to by 'val'.
 **
 **	The syntax which it accepts is pretty much what you would
 **	expect.  Basically, it is:
 **		{<sp>} [+|-] {<sp>} {<digit>} [.{digit}] {<sp>} [<exp>]
 **	where <exp> is "e" or "E" followed by an integer, <sp> is a
 **	space character, <digit> is zero through nine, [] is zero or
 **	one, and {} is zero or more.
 **
 **	Parameters:
 **		str -- string to convert.
 **		val -- pointer to place to put the result (which
 **			must be type double).
 **
 **	Returns:
 **		zero -- ok.
 **		-1 -- syntax error.
 **		+1 -- overflow (not implemented).
 **
 **	Side Effects:
 **		clobbers *val.
 */
int
atof1(char *str, double *val)
{
    register char	*p;
    double		v;
    double		fact;
    int		minus;
    register char	c;
    int		expon;
    register int	gotmant;
    
    v = 0.0;
    p = str;
    minus = 0;
    
    /* skip leading blanks */
    while ((c = *p) != '\0') {
	if (c != ' ')
	    break;
	p++;
    }
    
    /* handle possible sign */
    switch (c) {
    case '-':
	minus++;
	
    case '+':
	p++;
    }
    
    /* skip blanks after sign */
    while ((c = *p) != '\0') {
	if (c != ' ')
	    break;
	p++;
    }
    
    /* start collecting the number to the decimal point */
    gotmant = 0;
    for (;;) {
	c = *p;
	if (c < '0' || c > '9')
	    break;
	v = v * 10.0 + (c - '0');
	gotmant++;
	p++;
    }
    
    /* check for fractional part */
    if (c == '.') {
	fact = 1.0;
	for (;;) {
	    c = *++p;
	    if (c < '0' || c > '9')
		break;
	    fact *= 0.1;
	    v += (c - '0') * fact;
	    gotmant++;
	}
    }
    
    /* skip blanks before possible exponent */
    while ((c = *p) != '\0') {
	if (c != ' ')
	    break;
	p++;
    }
    
    /* test for exponent */
    if (c == 'e' || c == 'E') {
	p++;
	expon = pg_atoi(p, sizeof(expon), '\0');
	if (!gotmant)
	    v = 1.0;
	fact = expon;
	v *= pow(10.0, fact);
    } else {
	/* if no exponent, then nothing */
	if (c != 0)
	    return (-1);
    }
    
    /* store the result and exit */
    if (minus)
	v = -v;
    *val = v;
    return (0);
}