postgresql/src/backend/utils/adt/numutils.c

481 lines
8.7 KiB
C

/*-------------------------------------------------------------------------
*
* numutils.c
* utility functions for I/O of built-in numeric types.
*
* integer: pg_itoa, pg_ltoa
* floating point: ftoa, atof1
*
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/adt/numutils.c,v 1.43 2000/10/22 22:14:56 petere Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <errno.h>
#include <math.h>
#include <limits.h>
#include "utils/builtins.h"
#ifndef INT_MAX
#define INT_MAX (0x7FFFFFFFL)
#endif
#ifndef INT_MIN
#define INT_MIN (-INT_MAX-1)
#endif
#ifndef SHRT_MAX
#define SHRT_MAX (0x7FFF)
#endif
#ifndef SHRT_MIN
#define SHRT_MIN (-SHRT_MAX-1)
#endif
#ifndef SCHAR_MAX
#define SCHAR_MAX (0x7F)
#endif
#ifndef SCHAR_MIN
#define SCHAR_MIN (-SCHAR_MAX-1)
#endif
int32
pg_atoi(char *s, int size, int c)
{
long l = 0;
char *badp = (char *) NULL;
Assert(s);
errno = 0;
/*
* Some versions of strtol treat the empty string as an error. This
* code will explicitly return 0 for an empty string.
*/
if (s == (char *) NULL)
elog(ERROR, "pg_atoi: NULL pointer!");
else if (*s == 0)
l = (long) 0;
else
l = strtol(s, &badp, 10);
/*
* strtol() normally only sets ERANGE. On some systems it also
* may set EINVAL, which simply means it couldn't parse the
* input string. This is handled by the second "if" consistent
* across platforms.
*/
if (errno && errno != EINVAL)
elog(ERROR, "pg_atoi: error reading \"%s\": %m", s);
if (badp && *badp && (*badp != c))
elog(ERROR, "pg_atoi: error in \"%s\": can\'t parse \"%s\"", s, badp);
switch (size)
{
case sizeof(int32):
#if defined(HAVE_LONG_INT_64)
/* won't get ERANGE on these with 64-bit longs... */
if (l < INT_MIN)
{
errno = ERANGE;
elog(ERROR, "pg_atoi: error reading \"%s\": %m", s);
}
if (l > INT_MAX)
{
errno = ERANGE;
elog(ERROR, "pg_atoi: error reading \"%s\": %m", s);
}
#endif /* HAVE_LONG_INT_64 */
break;
case sizeof(int16):
if (l < SHRT_MIN)
{
errno = ERANGE;
elog(ERROR, "pg_atoi: error reading \"%s\": %m", s);
}
if (l > SHRT_MAX)
{
errno = ERANGE;
elog(ERROR, "pg_atoi: error reading \"%s\": %m", s);
}
break;
case sizeof(int8):
if (l < SCHAR_MIN)
{
errno = ERANGE;
elog(ERROR, "pg_atoi: error reading \"%s\": %m", s);
}
if (l > SCHAR_MAX)
{
errno = ERANGE;
elog(ERROR, "pg_atoi: error reading \"%s\": %m", s);
}
break;
default:
elog(ERROR, "pg_atoi: invalid result size: %d", size);
}
return (int32) l;
}
/*
* pg_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
pg_itoa(int16 i, char *a)
{
sprintf(a, "%hd", (short) i);
}
/*
* pg_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
pg_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').
*/
#ifdef NOT_USED
int
ftoa(double value, char *ascii, int width, int prec1, char format)
{
#ifndef HAVE_FCVT
char out[256];
char fmt[256];
int ret;
sprintf(fmt, "%%%d.%d%c", width, prec1, format);
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;
int avail = 0;
char *a = NULL;
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
}
#endif
/*
** 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.
*/
#ifdef NOT_USED
int
atof1(char *str, double *val)
{
char *p;
double v;
double fact;
int minus;
char c;
int expon;
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;
}
#endif