/*-------------------------------------------------------------------------
*
- * numutils.c--
+ * 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
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/utils/adt/numutils.c,v 1.24 1998/06/15 19:29:36 momjian Exp $
+ * src/backend/utils/adt/numutils.c
*
*-------------------------------------------------------------------------
*/
-#include <stdio.h> /* for sprintf() */
-#include <errno.h>
+#include "postgres.h"
+
#include <math.h>
-#ifdef HAVE_LIMITS
#include <limits.h>
-#endif
-#include "postgres.h"
-#include "utils/builtins.h" /* where the declarations go */
-#ifndef HAVE_MEMMOVE
-#include <regex/utils.h>
-#else
-#include <string.h>
-#endif
+#include <ctype.h>
-#ifndef INT_MAX
-#define INT_MAX (0x7FFFFFFFL)
-#endif
-#ifndef INT_MIN
-#define INT_MIN (-0x80000000L)
-#endif
-#ifndef SHRT_MAX
-#define SHRT_MAX (0x7FFF)
-#endif
-#ifndef SHRT_MIN
-#define SHRT_MIN (-0x8000)
-#endif
-#ifndef SCHAR_MAX
-#define SCHAR_MAX (0x7F)
-#endif
-#ifndef SCHAR_MIN
-#define SCHAR_MIN (-0x80)
-#endif
+#include "utils/builtins.h"
+/*
+ * pg_atoi: convert string to integer
+ *
+ * allows any number of leading or trailing whitespace characters.
+ *
+ * 'size' is the sizeof() the desired integral result (1, 2, or 4 bytes).
+ *
+ * c, if not 0, is a terminator character that may appear after the
+ * integer (plus whitespace). If 0, the string must end after the integer.
+ *
+ * Unlike plain atoi(), this will throw ereport() upon bad input format or
+ * overflow.
+ */
int32
pg_atoi(char *s, int size, int c)
{
long l;
- char *badp = (char *) NULL;
-
- Assert(s);
+ char *badp;
+
+ /*
+ * Some versions of strtol treat the empty string as an error, but some
+ * seem not to. Make an explicit test to be sure we catch it.
+ */
+ if (s == NULL)
+ elog(ERROR, "NULL pointer");
+ if (*s == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for integer: \"%s\"",
+ s)));
errno = 0;
l = strtol(s, &badp, 10);
- if (errno) /* strtol must set ERANGE */
- 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);
+
+ /* We made no progress parsing the string, so bail out */
+ if (s == badp)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for integer: \"%s\"",
+ s)));
switch (size)
{
case sizeof(int32):
-#ifdef HAS_LONG_LONG
+ if (errno == ERANGE
+#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 /* HAS_LONG_LONG */
+ || l < INT_MIN || l > INT_MAX
+#endif
+ )
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value \"%s\" is out of range for type integer", s)));
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);
- }
+ if (errno == ERANGE || l < SHRT_MIN || l > SHRT_MAX)
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value \"%s\" is out of range for type smallint", 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);
- }
+ if (errno == ERANGE || l < SCHAR_MIN || l > SCHAR_MAX)
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("value \"%s\" is out of range for 8-bit integer", s)));
break;
default:
- elog(ERROR, "pg_atoi: invalid result size: %d", size);
+ elog(ERROR, "unsupported result size: %d", size);
}
- return ((int32) l);
+
+ /*
+ * Skip any trailing whitespace; if anything but whitespace remains before
+ * the terminating character, bail out
+ */
+ while (*badp && *badp != c && isspace((unsigned char) *badp))
+ badp++;
+
+ if (*badp && *badp != c)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for integer: \"%s\"",
+ s)));
+
+ return (int32) l;
}
/*
- * itoa - converts a short int to its string represention
+ * pg_itoa: converts a signed 16-bit integer to its string representation
*
- * Note:
- * previously based on ~ingres/source/gutil/atoi.c
- * now uses vendor's sprintf conversion
+ * Caller must ensure that 'a' points to enough memory to hold the result
+ * (at least 7 bytes, counting a leading sign and trailing NUL).
+ *
+ * It doesn't seem worth implementing this separately.
*/
void
-itoa(int i, char *a)
+pg_itoa(int16 i, char *a)
{
- sprintf(a, "%hd", (short) i);
+ pg_ltoa((int32) i, a);
}
/*
- * ltoa - converts a long int to its string represention
+ * pg_ltoa: converts a signed 32-bit integer to its string representation
*
- * Note:
- * previously based on ~ingres/source/gutil/atoi.c
- * now uses vendor's sprintf conversion
+ * Caller must ensure that 'a' points to enough memory to hold the result
+ * (at least 12 bytes, counting a leading sign and trailing NUL).
*/
void
-ltoa(int32 l, char *a)
+pg_ltoa(int32 value, char *a)
{
- sprintf(a, "%d", l);
-}
+ char *start = a;
+ bool neg = false;
-/*
- ** 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)
+ /*
+ * Avoid problems with the most negative integer not being representable
+ * as a positive integer.
+ */
+ if (value == (-2147483647 - 1))
{
- MemSet(ascii, '*', width - 2);
- ascii[width] = 0;
- return (0);
+ memcpy(a, "-2147483648", 12);
+ return;
}
- 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')
+ else if (value < 0)
{
- /* 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--;
- }
+ value = -value;
+ neg = true;
}
- /* check for field too small */
- if (mode == 'F' || avail < prec)
+ /* Compute the result string backwards. */
+ do
{
- /* sorry joker, you lose */
- a = ascii;
- for (avail = width; avail > 0; avail--)
- *a++ = '*';
- *a = 0;
- return (0);
- }
+ int32 remainder;
+ int32 oldval = value;
- /* it fits; output the number */
- mode = 'E';
+ value /= 10;
+ remainder = oldval - value * 10;
+ *a++ = '0' + remainder;
+ } while (value != 0);
- /* output the LHS single digit */
- *a++ = *p++;
- expon--;
+ if (neg)
+ *a++ = '-';
- /* output the rhs */
- avail = 1;
+ /* Add trailing NUL byte, and back up 'a' to the last character. */
+ *a-- = '\0';
-frac_out:
- *a++ = '.';
- while (prec > 0)
+ /* Reverse string. */
+ while (start < a)
{
- prec--;
- if (avail < 0)
- {
- avail++;
- *a++ = '0';
- }
- else
- {
- if (*p)
- *a++ = *p++;
- else
- *a++ = '0';
- }
- }
+ char swap = *start;
- /* 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';
+ *start++ = *a;
+ *a-- = swap;
}
-
- /* 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.
+ * pg_lltoa: convert a signed 64-bit integer to its string representation
+ *
+ * Caller must ensure that 'a' points to enough memory to hold the result
+ * (at least MAXINT8LEN+1 bytes, counting a leading sign and trailing NUL).
*/
-#ifdef NOT_USED
-int
-atof1(char *str, double *val)
+void
+pg_lltoa(int64 value, char *a)
{
- char *p;
- double v;
- double fact;
- int minus;
- char c;
- int expon;
- int gotmant;
+ char *start = a;
+ bool neg = false;
- v = 0.0;
- p = str;
- minus = 0;
-
- /* skip leading blanks */
- while ((c = *p) != '\0')
+ /*
+ * Avoid problems with the most negative integer not being representable
+ * as a positive integer.
+ */
+ if (value == (-INT64CONST(0x7FFFFFFFFFFFFFFF) - 1))
{
- if (c != ' ')
- break;
- p++;
+ memcpy(a, "-9223372036854775808", 21);
+ return;
}
-
- /* handle possible sign */
- switch (c)
+ else if (value < 0)
{
- case '-':
- minus++;
-
- case '+':
- p++;
+ value = -value;
+ neg = true;
}
- /* skip blanks after sign */
- while ((c = *p) != '\0')
+ /* Compute the result string backwards. */
+ do
{
- if (c != ' ')
- break;
- p++;
- }
+ int64 remainder;
+ int64 oldval = value;
- /* 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++;
- }
+ value /= 10;
+ remainder = oldval - value * 10;
+ *a++ = '0' + remainder;
+ } while (value != 0);
- /* 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++;
- }
- }
+ if (neg)
+ *a++ = '-';
- /* skip blanks before possible exponent */
- while ((c = *p) != '\0')
- {
- if (c != ' ')
- break;
- p++;
- }
+ /* Add trailing NUL byte, and back up 'a' to the last character. */
+ *a-- = '\0';
- /* 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
+ /* Reverse string. */
+ while (start < a)
{
- /* if no exponent, then nothing */
- if (c != 0)
- return (-1);
- }
+ char swap = *start;
- /* store the result and exit */
- if (minus)
- v = -v;
- *val = v;
- return (0);
+ *start++ = *a;
+ *a-- = swap;
+ }
}
-
-#endif