From 9a6cc06ffabbdc6c455ce11dc07b2a501b3c22bd Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Thu, 19 Sep 2019 11:51:41 -0700 Subject: [PATCH] Move int->string routines to src/common, and expand. Author: Reviewed-By: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/access/common/printsimple.c | 4 +- src/backend/utils/adt/datetime.c | 100 ++++---- src/backend/utils/adt/int.c | 6 +- src/backend/utils/adt/int8.c | 7 +- src/backend/utils/adt/numutils.c | 284 +--------------------- src/backend/utils/sort/logtape.c | 5 +- src/common/string.c | 302 ++++++++++++++++++++++++ src/include/common/string.h | 87 +++++++ src/include/utils/builtins.h | 5 - 9 files changed, 451 insertions(+), 349 deletions(-) diff --git a/src/backend/access/common/printsimple.c b/src/backend/access/common/printsimple.c index 651ade14dd..2075909080 100644 --- a/src/backend/access/common/printsimple.c +++ b/src/backend/access/common/printsimple.c @@ -104,7 +104,7 @@ printsimple(TupleTableSlot *slot, DestReceiver *self) int32 num = DatumGetInt32(value); char str[12]; /* sign, 10 digits and '\0' */ - pg_ltoa(num, str); + pg_int32tostr(str, num); pq_sendcountedtext(&buf, str, strlen(str), false); } break; @@ -114,7 +114,7 @@ printsimple(TupleTableSlot *slot, DestReceiver *self) int64 num = DatumGetInt64(value); char str[23]; /* sign, 21 digits and '\0' */ - pg_lltoa(num, str); + pg_int64tostr(str, num); pq_sendcountedtext(&buf, str, strlen(str), false); } break; diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index e38bd93054..82bccd82cc 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.c @@ -391,9 +391,9 @@ AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros) Assert(precision >= 0); if (fillzeros) - cp = pg_ltostr_zeropad(cp, Abs(sec), 2); + cp = pg_int32tostr_nn_zeropad(cp, Abs(sec), 2); else - cp = pg_ltostr(cp, Abs(sec)); + cp = pg_int32tostr_nn(cp, Abs(sec)); /* fsec_t is just an int32 */ if (fsec != 0) @@ -429,11 +429,11 @@ AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros) /* * If we still have a non-zero value then precision must have not been - * enough to print the number. We punt the problem to pg_ltostr(), + * enough to print the number. We punt the problem to pg_int32tostr_nn(), * which will generate a correct answer in the minimum valid width. */ if (value) - return pg_ltostr(cp, Abs(fsec)); + return pg_int32tostr_nn(cp, Abs(fsec)); return end; } @@ -3834,20 +3834,20 @@ EncodeTimezone(char *str, int tz, int style) if (sec != 0) { - str = pg_ltostr_zeropad(str, hour, 2); + str = pg_int32tostr_nn_zeropad(str, hour, 2); *str++ = ':'; - str = pg_ltostr_zeropad(str, min, 2); + str = pg_int32tostr_nn_zeropad(str, min, 2); *str++ = ':'; - str = pg_ltostr_zeropad(str, sec, 2); + str = pg_int32tostr_nn_zeropad(str, sec, 2); } else if (min != 0 || style == USE_XSD_DATES) { - str = pg_ltostr_zeropad(str, hour, 2); + str = pg_int32tostr_nn_zeropad(str, hour, 2); *str++ = ':'; - str = pg_ltostr_zeropad(str, min, 2); + str = pg_int32tostr_nn_zeropad(str, min, 2); } else - str = pg_ltostr_zeropad(str, hour, 2); + str = pg_int32tostr_nn_zeropad(str, hour, 2); return str; } @@ -3864,40 +3864,40 @@ EncodeDateOnly(struct pg_tm *tm, int style, char *str) case USE_ISO_DATES: case USE_XSD_DATES: /* compatible with ISO date formats */ - str = pg_ltostr_zeropad(str, + str = pg_int32tostr_nn_zeropad(str, (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4); *str++ = '-'; - str = pg_ltostr_zeropad(str, tm->tm_mon, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_mon, 2); *str++ = '-'; - str = pg_ltostr_zeropad(str, tm->tm_mday, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_mday, 2); break; case USE_SQL_DATES: /* compatible with Oracle/Ingres date formats */ if (DateOrder == DATEORDER_DMY) { - str = pg_ltostr_zeropad(str, tm->tm_mday, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_mday, 2); *str++ = '/'; - str = pg_ltostr_zeropad(str, tm->tm_mon, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_mon, 2); } else { - str = pg_ltostr_zeropad(str, tm->tm_mon, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_mon, 2); *str++ = '/'; - str = pg_ltostr_zeropad(str, tm->tm_mday, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_mday, 2); } *str++ = '/'; - str = pg_ltostr_zeropad(str, + str = pg_int32tostr_nn_zeropad(str, (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4); break; case USE_GERMAN_DATES: /* German-style date format */ - str = pg_ltostr_zeropad(str, tm->tm_mday, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_mday, 2); *str++ = '.'; - str = pg_ltostr_zeropad(str, tm->tm_mon, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_mon, 2); *str++ = '.'; - str = pg_ltostr_zeropad(str, + str = pg_int32tostr_nn_zeropad(str, (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4); break; @@ -3906,18 +3906,18 @@ EncodeDateOnly(struct pg_tm *tm, int style, char *str) /* traditional date-only style for Postgres */ if (DateOrder == DATEORDER_DMY) { - str = pg_ltostr_zeropad(str, tm->tm_mday, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_mday, 2); *str++ = '-'; - str = pg_ltostr_zeropad(str, tm->tm_mon, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_mon, 2); } else { - str = pg_ltostr_zeropad(str, tm->tm_mon, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_mon, 2); *str++ = '-'; - str = pg_ltostr_zeropad(str, tm->tm_mday, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_mday, 2); } *str++ = '-'; - str = pg_ltostr_zeropad(str, + str = pg_int32tostr_nn_zeropad(str, (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4); break; } @@ -3942,9 +3942,9 @@ EncodeDateOnly(struct pg_tm *tm, int style, char *str) void EncodeTimeOnly(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, int style, char *str) { - str = pg_ltostr_zeropad(str, tm->tm_hour, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_hour, 2); *str++ = ':'; - str = pg_ltostr_zeropad(str, tm->tm_min, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_min, 2); *str++ = ':'; str = AppendSeconds(str, tm->tm_sec, fsec, MAX_TIME_PRECISION, true); if (print_tz) @@ -3987,16 +3987,16 @@ EncodeDateTime(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, const char case USE_ISO_DATES: case USE_XSD_DATES: /* Compatible with ISO-8601 date formats */ - str = pg_ltostr_zeropad(str, + str = pg_int32tostr_nn_zeropad(str, (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4); *str++ = '-'; - str = pg_ltostr_zeropad(str, tm->tm_mon, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_mon, 2); *str++ = '-'; - str = pg_ltostr_zeropad(str, tm->tm_mday, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_mday, 2); *str++ = (style == USE_ISO_DATES) ? ' ' : 'T'; - str = pg_ltostr_zeropad(str, tm->tm_hour, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_hour, 2); *str++ = ':'; - str = pg_ltostr_zeropad(str, tm->tm_min, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_min, 2); *str++ = ':'; str = AppendTimestampSeconds(str, tm, fsec); if (print_tz) @@ -4007,23 +4007,23 @@ EncodeDateTime(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, const char /* Compatible with Oracle/Ingres date formats */ if (DateOrder == DATEORDER_DMY) { - str = pg_ltostr_zeropad(str, tm->tm_mday, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_mday, 2); *str++ = '/'; - str = pg_ltostr_zeropad(str, tm->tm_mon, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_mon, 2); } else { - str = pg_ltostr_zeropad(str, tm->tm_mon, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_mon, 2); *str++ = '/'; - str = pg_ltostr_zeropad(str, tm->tm_mday, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_mday, 2); } *str++ = '/'; - str = pg_ltostr_zeropad(str, + str = pg_int32tostr_nn_zeropad(str, (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4); *str++ = ' '; - str = pg_ltostr_zeropad(str, tm->tm_hour, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_hour, 2); *str++ = ':'; - str = pg_ltostr_zeropad(str, tm->tm_min, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_min, 2); *str++ = ':'; str = AppendTimestampSeconds(str, tm, fsec); @@ -4046,16 +4046,16 @@ EncodeDateTime(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, const char case USE_GERMAN_DATES: /* German variant on European style */ - str = pg_ltostr_zeropad(str, tm->tm_mday, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_mday, 2); *str++ = '.'; - str = pg_ltostr_zeropad(str, tm->tm_mon, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_mon, 2); *str++ = '.'; - str = pg_ltostr_zeropad(str, + str = pg_int32tostr_nn_zeropad(str, (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4); *str++ = ' '; - str = pg_ltostr_zeropad(str, tm->tm_hour, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_hour, 2); *str++ = ':'; - str = pg_ltostr_zeropad(str, tm->tm_min, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_min, 2); *str++ = ':'; str = AppendTimestampSeconds(str, tm, fsec); @@ -4081,7 +4081,7 @@ EncodeDateTime(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, const char *str++ = ' '; if (DateOrder == DATEORDER_DMY) { - str = pg_ltostr_zeropad(str, tm->tm_mday, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_mday, 2); *str++ = ' '; memcpy(str, months[tm->tm_mon - 1], 3); str += 3; @@ -4091,16 +4091,16 @@ EncodeDateTime(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, const char memcpy(str, months[tm->tm_mon - 1], 3); str += 3; *str++ = ' '; - str = pg_ltostr_zeropad(str, tm->tm_mday, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_mday, 2); } *str++ = ' '; - str = pg_ltostr_zeropad(str, tm->tm_hour, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_hour, 2); *str++ = ':'; - str = pg_ltostr_zeropad(str, tm->tm_min, 2); + str = pg_int32tostr_nn_zeropad(str, tm->tm_min, 2); *str++ = ':'; str = AppendTimestampSeconds(str, tm, fsec); *str++ = ' '; - str = pg_ltostr_zeropad(str, + str = pg_int32tostr_nn_zeropad(str, (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4); if (print_tz) diff --git a/src/backend/utils/adt/int.c b/src/backend/utils/adt/int.c index 04825fc77d..016dd0f03c 100644 --- a/src/backend/utils/adt/int.c +++ b/src/backend/utils/adt/int.c @@ -76,7 +76,7 @@ int2out(PG_FUNCTION_ARGS) int16 arg1 = PG_GETARG_INT16(0); char *result = (char *) palloc(7); /* sign, 5 digits, '\0' */ - pg_itoa(arg1, result); + pg_int16tostr(result, arg1); PG_RETURN_CSTRING(result); } @@ -191,7 +191,7 @@ int2vectorout(PG_FUNCTION_ARGS) { if (num != 0) *rp++ = ' '; - pg_itoa(int2Array->values[num], rp); + pg_int16tostr(rp, int2Array->values[num]); while (*++rp != '\0') ; } @@ -281,7 +281,7 @@ int4out(PG_FUNCTION_ARGS) int32 arg1 = PG_GETARG_INT32(0); char *result = (char *) palloc(12); /* sign, 10 digits, '\0' */ - pg_ltoa(arg1, result); + pg_int32tostr(result, arg1); PG_RETURN_CSTRING(result); } diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c index 0ff9394a2f..88bea4978a 100644 --- a/src/backend/utils/adt/int8.c +++ b/src/backend/utils/adt/int8.c @@ -18,6 +18,7 @@ #include #include "common/int.h" +#include "common/string.h" #include "funcapi.h" #include "libpq/pqformat.h" #include "nodes/nodeFuncs.h" @@ -27,8 +28,6 @@ #include "utils/builtins.h" -#define MAXINT8LEN 25 - typedef struct { int64 current; @@ -149,10 +148,10 @@ Datum int8out(PG_FUNCTION_ARGS) { int64 val = PG_GETARG_INT64(0); - char buf[MAXINT8LEN + 1]; + char buf[MAXINT64LEN + 1]; char *result; - pg_lltoa(val, buf); + pg_int64tostr(buf, val); result = pstrdup(buf); PG_RETURN_CSTRING(result); } diff --git a/src/backend/utils/adt/numutils.c b/src/backend/utils/adt/numutils.c index 70138feb29..e1801648cb 100644 --- a/src/backend/utils/adt/numutils.c +++ b/src/backend/utils/adt/numutils.c @@ -19,6 +19,7 @@ #include #include "common/int.h" +#include "common/string.h" #include "utils/builtins.h" /* @@ -261,289 +262,6 @@ invalid_syntax: return 0; /* keep compiler quiet */ } -/* - * pg_itoa: converts a signed 16-bit integer to its string representation - * - * 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 -pg_itoa(int16 i, char *a) -{ - pg_ltoa((int32) i, a); -} - -/* - * pg_ltoa: converts a signed 32-bit integer to its string representation - * - * 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 -pg_ltoa(int32 value, char *a) -{ - char *start = a; - bool neg = false; - - /* - * Avoid problems with the most negative integer not being representable - * as a positive integer. - */ - if (value == PG_INT32_MIN) - { - memcpy(a, "-2147483648", 12); - return; - } - else if (value < 0) - { - value = -value; - neg = true; - } - - /* Compute the result string backwards. */ - do - { - int32 remainder; - int32 oldval = value; - - value /= 10; - remainder = oldval - value * 10; - *a++ = '0' + remainder; - } while (value != 0); - - if (neg) - *a++ = '-'; - - /* Add trailing NUL byte, and back up 'a' to the last character. */ - *a-- = '\0'; - - /* Reverse string. */ - while (start < a) - { - char swap = *start; - - *start++ = *a; - *a-- = swap; - } -} - -/* - * 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). - */ -void -pg_lltoa(int64 value, char *a) -{ - char *start = a; - bool neg = false; - - /* - * Avoid problems with the most negative integer not being representable - * as a positive integer. - */ - if (value == PG_INT64_MIN) - { - memcpy(a, "-9223372036854775808", 21); - return; - } - else if (value < 0) - { - value = -value; - neg = true; - } - - /* Compute the result string backwards. */ - do - { - int64 remainder; - int64 oldval = value; - - value /= 10; - remainder = oldval - value * 10; - *a++ = '0' + remainder; - } while (value != 0); - - if (neg) - *a++ = '-'; - - /* Add trailing NUL byte, and back up 'a' to the last character. */ - *a-- = '\0'; - - /* Reverse string. */ - while (start < a) - { - char swap = *start; - - *start++ = *a; - *a-- = swap; - } -} - - -/* - * pg_ltostr_zeropad - * Converts 'value' into a decimal string representation stored at 'str'. - * 'minwidth' specifies the minimum width of the result; any extra space - * is filled up by prefixing the number with zeros. - * - * Returns the ending address of the string result (the last character written - * plus 1). Note that no NUL terminator is written. - * - * The intended use-case for this function is to build strings that contain - * multiple individual numbers, for example: - * - * str = pg_ltostr_zeropad(str, hours, 2); - * *str++ = ':'; - * str = pg_ltostr_zeropad(str, mins, 2); - * *str++ = ':'; - * str = pg_ltostr_zeropad(str, secs, 2); - * *str = '\0'; - * - * Note: Caller must ensure that 'str' points to enough memory to hold the - * result. - */ -char * -pg_ltostr_zeropad(char *str, int32 value, int32 minwidth) -{ - char *start = str; - char *end = &str[minwidth]; - int32 num = value; - - Assert(minwidth > 0); - - /* - * Handle negative numbers in a special way. We can't just write a '-' - * prefix and reverse the sign as that would overflow for INT32_MIN. - */ - if (num < 0) - { - *start++ = '-'; - minwidth--; - - /* - * Build the number starting at the last digit. Here remainder will - * be a negative number, so we must reverse the sign before adding '0' - * in order to get the correct ASCII digit. - */ - while (minwidth--) - { - int32 oldval = num; - int32 remainder; - - num /= 10; - remainder = oldval - num * 10; - start[minwidth] = '0' - remainder; - } - } - else - { - /* Build the number starting at the last digit */ - while (minwidth--) - { - int32 oldval = num; - int32 remainder; - - num /= 10; - remainder = oldval - num * 10; - start[minwidth] = '0' + remainder; - } - } - - /* - * If minwidth was not high enough to fit the number then num won't have - * been divided down to zero. We punt the problem to pg_ltostr(), which - * will generate a correct answer in the minimum valid width. - */ - if (num != 0) - return pg_ltostr(str, value); - - /* Otherwise, return last output character + 1 */ - return end; -} - -/* - * pg_ltostr - * Converts 'value' into a decimal string representation stored at 'str'. - * - * Returns the ending address of the string result (the last character written - * plus 1). Note that no NUL terminator is written. - * - * The intended use-case for this function is to build strings that contain - * multiple individual numbers, for example: - * - * str = pg_ltostr(str, a); - * *str++ = ' '; - * str = pg_ltostr(str, b); - * *str = '\0'; - * - * Note: Caller must ensure that 'str' points to enough memory to hold the - * result. - */ -char * -pg_ltostr(char *str, int32 value) -{ - char *start; - char *end; - - /* - * Handle negative numbers in a special way. We can't just write a '-' - * prefix and reverse the sign as that would overflow for INT32_MIN. - */ - if (value < 0) - { - *str++ = '-'; - - /* Mark the position we must reverse the string from. */ - start = str; - - /* Compute the result string backwards. */ - do - { - int32 oldval = value; - int32 remainder; - - value /= 10; - remainder = oldval - value * 10; - /* As above, we expect remainder to be negative. */ - *str++ = '0' - remainder; - } while (value != 0); - } - else - { - /* Mark the position we must reverse the string from. */ - start = str; - - /* Compute the result string backwards. */ - do - { - int32 oldval = value; - int32 remainder; - - value /= 10; - remainder = oldval - value * 10; - *str++ = '0' + remainder; - } while (value != 0); - } - - /* Remember the end+1 and back up 'str' to the last character. */ - end = str--; - - /* Reverse string. */ - while (start < str) - { - char swap = *start; - - *start++ = *str; - *str-- = swap; - } - - return end; -} - /* * pg_strtouint64 * Converts 'str' into an unsigned 64-bit integer. diff --git a/src/backend/utils/sort/logtape.c b/src/backend/utils/sort/logtape.c index 8985b9e095..5e73f2b7b2 100644 --- a/src/backend/utils/sort/logtape.c +++ b/src/backend/utils/sort/logtape.c @@ -82,6 +82,7 @@ #include "postgres.h" +#include "common/string.h" #include "storage/buffile.h" #include "utils/builtins.h" #include "utils/logtape.h" @@ -430,7 +431,7 @@ ltsConcatWorkerTapes(LogicalTapeSet *lts, TapeShare *shared, lt = <s->tapes[i]; - pg_itoa(i, filename); + pg_int16tostr(filename, i); file = BufFileOpenShared(fileset, filename); filesize = BufFileSize(file); @@ -568,7 +569,7 @@ LogicalTapeSetCreate(int ntapes, TapeShare *shared, SharedFileSet *fileset, { char filename[MAXPGPATH]; - pg_itoa(worker, filename); + pg_int16tostr(filename, worker); lts->pfile = BufFileCreateShared(fileset, filename); } else diff --git a/src/common/string.c b/src/common/string.c index c9b8482cb0..3e0eff9caf 100644 --- a/src/common/string.c +++ b/src/common/string.c @@ -112,3 +112,305 @@ pg_strip_crlf(char *str) return len; } + +/* see pg_int32tostr_nn */ +char * +pg_int16tostr_nn(char *str, int16 value) +{ + /* It doesn't seem worth implementing this separately. */ + return pg_int32tostr_nn(str, (int32) value); +} + +/* + * pg_int32tostr_nn + * Converts 'value' into a decimal string representation stored at 'str'. + * + * Returns the ending address of the string result (the last character written + * plus 1). Note that no NUL terminator is written. + * + * The intended use-case for this function is to build strings that contain + * multiple individual numbers, for example: + * + * str = pg_int32tostr_nn(str, a); + * *str++ = ' '; + * str = pg_int32tostr_nn(str, b); + * *str = '\0'; + * + * Note: Caller must ensure that 'str' points to enough memory to hold the + * result. + */ +char * +pg_int32tostr_nn(char *str, int32 value) +{ + char *start; + char *end; + + /* + * Handle negative numbers in a special way. We can't just write a '-' + * prefix and reverse the sign as that would overflow for INT32_MIN. + */ + if (value < 0) + { + *str++ = '-'; + + /* Mark the position we must reverse the string from. */ + start = str; + + /* Compute the result string backwards. */ + do + { + int32 oldval = value; + int32 remainder; + + value /= 10; + remainder = oldval - value * 10; + /* As above, we expect remainder to be negative. */ + *str++ = '0' - remainder; + } while (value != 0); + } + else + { + /* Mark the position we must reverse the string from. */ + start = str; + + /* Compute the result string backwards. */ + do + { + int32 oldval = value; + int32 remainder; + + value /= 10; + remainder = oldval - value * 10; + *str++ = '0' + remainder; + } while (value != 0); + } + + /* Remember the end+1 and back up 'str' to the last character. */ + end = str--; + + /* Reverse string. */ + while (start < str) + { + char swap = *start; + + *start++ = *str; + *str-- = swap; + } + + return end; +} + +/* see pg_int32tostr_nn, except for unsigned */ +char * +pg_uint32tostr_nn(char *str, uint32 value) +{ + char *start; + char *end; + + /* Mark the position we must reverse the string from. */ + start = str; + + /* Compute the result string backwards. */ + do + { + uint32 oldval = value; + uint32 remainder; + + value /= 10; + remainder = oldval - value * 10; + *str++ = '0' + remainder; + } while (value != 0); + + /* Remember the end+1 and back up 'str' to the last character. */ + end = str--; + + /* Reverse string. */ + while (start < str) + { + char swap = *start; + + *start++ = *str; + *str-- = swap; + } + + return end; +} + +/* see pg_int32tostr_nn */ +char * +pg_int64tostr_nn(char *str, int64 value) +{ + char *start; + char *end; + + /* + * Handle negative numbers in a special way. We can't just write a '-' + * prefix and reverse the sign as that would overflow for INT64_MIN. + */ + if (value < 0) + { + *str++ = '-'; + + /* Mark the position we must reverse the string from. */ + start = str; + + /* Compute the result string backwards. */ + do + { + int64 oldval = value; + int64 remainder; + + value /= 10; + remainder = oldval - value * 10; + /* As above, we expect remainder to be negative. */ + *str++ = '0' - remainder; + } while (value != 0); + } + else + { + /* Mark the position we must reverse the string from. */ + start = str; + + /* Compute the result string backwards. */ + do + { + int64 oldval = value; + int64 remainder; + + value /= 10; + remainder = oldval - value * 10; + *str++ = '0' + remainder; + } while (value != 0); + } + + /* Remember the end+1 and back up 'str' to the last character. */ + end = str--; + + /* Reverse string. */ + while (start < str) + { + char swap = *start; + + *start++ = *str; + *str-- = swap; + } + + return end; +} + +/* see pg_int64tostr_nn, except for unsigned */ +char * +pg_uint64tostr_nn(char *str, uint64 value) +{ + char *start; + char *end; + + /* Mark the position we must reverse the string from. */ + start = str; + + /* Compute the result string backwards. */ + do + { + uint64 oldval = value; + uint64 remainder; + + value /= 10; + remainder = oldval - value * 10; + *str++ = '0' + remainder; + } while (value != 0); + + /* Remember the end+1 and back up 'str' to the last character. */ + end = str--; + + /* Reverse string. */ + while (start < str) + { + char swap = *start; + + *start++ = *str; + *str-- = swap; + } + + return end; +} + +/* + * pg_int32tostr_nn_zeropad + * Converts 'value' into a decimal string representation stored at 'str'. + * 'minwidth' specifies the minimum width of the result; any extra space + * is filled up by prefixing the number with zeros. + * + * Returns the ending address of the string result (the last character written + * plus 1). Note that no NUL terminator is written. + * + * The intended use-case for this function is to build strings that contain + * multiple individual numbers, for example: + * + * str = pg_int32tostr_nn_zeropad(str, hours, 2); + * *str++ = ':'; + * str = pg_int32tostr_nn_zeropad(str, mins, 2); + * *str++ = ':'; + * str = pg_int32tostr_nn_zeropad(str, secs, 2); + * *str = '\0'; + * + * Note: Caller must ensure that 'str' points to enough memory to hold the + * result. + */ +char * +pg_int32tostr_nn_zeropad(char *str, int32 value, int32 minwidth) +{ + char *start = str; + char *end = &str[minwidth]; + int32 num = value; + + Assert(minwidth > 0); + + /* + * Handle negative numbers in a special way. We can't just write a '-' + * prefix and reverse the sign as that would overflow for INT32_MIN. + */ + if (num < 0) + { + *start++ = '-'; + minwidth--; + + /* + * Build the number starting at the last digit. Here remainder will + * be a negative number, so we must reverse the sign before adding '0' + * in order to get the correct ASCII digit. + */ + while (minwidth--) + { + int32 oldval = num; + int32 remainder; + + num /= 10; + remainder = oldval - num * 10; + start[minwidth] = '0' - remainder; + } + } + else + { + /* Build the number starting at the last digit */ + while (minwidth--) + { + int32 oldval = num; + int32 remainder; + + num /= 10; + remainder = oldval - num * 10; + start[minwidth] = '0' + remainder; + } + } + + /* + * If minwidth was not high enough to fit the number then num won't have + * been divided down to zero. We punt the problem to pg_int32tostr_nn(), which + * will generate a correct answer in the minimum valid width. + */ + if (num != 0) + return pg_int32tostr_nn(str, value); + + /* Otherwise, return last output character + 1 */ + return end; +} diff --git a/src/include/common/string.h b/src/include/common/string.h index 94f653fdd7..f5604535ba 100644 --- a/src/include/common/string.h +++ b/src/include/common/string.h @@ -16,4 +16,91 @@ extern int strtoint(const char *pg_restrict str, char **pg_restrict endptr, extern void pg_clean_ascii(char *str); extern int pg_strip_crlf(char *str); +#define MAXINT16LEN sizeof(CppAsString(PG_INT16_MIN)) +#define MAXINT32LEN sizeof(CppAsString(PG_INT32_MIN)) +#define MAXINT64LEN sizeof(CppAsString(PG_INT64_MIN)) + +extern char *pg_int16tostr_nn(char *str, int16 value); +extern char *pg_uint16tostr_nn(char *str, int16 value); +extern char *pg_int32tostr_nn(char *str, int32 value); +extern char *pg_uint32tostr_nn(char *str, uint32 value); +extern char *pg_int32tostr_nn_zeropad(char *str, int32 value, int32 minwidth); +extern char *pg_int64tostr_nn(char *str, int64 value); +extern char *pg_uint64tostr_nn(char *str, uint64 value); + +/* + * pg_intNNtostr: converts a signed NN-bit integer to its string representation + * + * Caller must ensure that 'a' points to enough memory to hold the result + * (at least MAXINTNNLEN bytes, counting a leading sign and trailing NUL). + */ + +static inline char * +pg_int16tostr(char *str, int16 value) +{ + char *end = pg_int16tostr_nn(str, value); + + *end = '\0'; + + return end; +} + +static inline char * +pg_int32tostr(char *str, int32 value) +{ + char *end = pg_int32tostr_nn(str, value); + + *end = '\0'; + + return end; +} + +static inline char * +pg_int64tostr(char *str, int64 value) +{ + char *end = pg_int64tostr_nn(str, value); + + *end = '\0'; + + return end; +} + + +/* + * pg_uintNNtostr: converts a un signed NN-bit integer to its string representation + * + * Caller must ensure that 'a' points to enough memory to hold the result + * (at least MAXINTNNLEN bytes, counting the trailing NUL). + */ + +static inline char * +pg_uint16tostr(char *str, uint16 value) +{ + char *end = pg_uint16tostr_nn(str, value); + + *end = '\0'; + + return end; +} + +static inline char * +pg_uint32tostr(char *str, uint32 value) +{ + char *end = pg_uint32tostr_nn(str, value); + + *end = '\0'; + + return end; +} + +static inline char * +pg_uint64tostr(char *str, uint64 value) +{ + char *end = pg_uint64tostr_nn(str, value); + + *end = '\0'; + + return end; +} + #endif /* COMMON_STRING_H */ diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 937ddb7ef0..2aee5740b9 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -45,11 +45,6 @@ extern int namestrcmp(Name name, const char *str); extern int32 pg_atoi(const char *s, int size, int c); extern int16 pg_strtoint16(const char *s); extern int32 pg_strtoint32(const char *s); -extern void pg_itoa(int16 i, char *a); -extern void pg_ltoa(int32 l, char *a); -extern void pg_lltoa(int64 ll, char *a); -extern char *pg_ltostr_zeropad(char *str, int32 value, int32 minwidth); -extern char *pg_ltostr(char *str, int32 value); extern uint64 pg_strtouint64(const char *str, char **endptr, int base); /* oid.c */ -- 2.39.5