aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/formatting.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2024-09-26 11:02:31 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2024-09-26 11:02:31 -0400
commit147bbc90f75794e5522dcbadaf2bbe1af3ce574a (patch)
treee84f92a11a5b766109dc51fe062d56e87cf41030 /src/backend/utils/adt/formatting.c
parente3a92ab0708aa8ac0c8466312cef316ea6d03c63 (diff)
downloadpostgresql-147bbc90f75794e5522dcbadaf2bbe1af3ce574a.tar.gz
postgresql-147bbc90f75794e5522dcbadaf2bbe1af3ce574a.zip
Modernize to_char's Roman-numeral code, fixing overflow problems.
int_to_roman() only accepts plain "int" input, which is fine since we're going to produce '###############' for any value above 3999 anyway. However, the numeric and int8 variants of to_char() would throw an error if the given input exceeded the integer range, while the float-input variants invoked undefined-per-C-standard behavior. Fix things so that you uniformly get '###############' for out of range input. Also add test cases covering this code, plus the equally-untested EEEE, V, and PL format codes. Discussion: https://postgr.es/m/2956175.1725831136@sss.pgh.pa.us
Diffstat (limited to 'src/backend/utils/adt/formatting.c')
-rw-r--r--src/backend/utils/adt/formatting.c92
1 files changed, 69 insertions, 23 deletions
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 68fa89418ff..85a7dd45619 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -5189,6 +5189,11 @@ NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree)
}
+/*
+ * Convert integer to Roman numerals
+ * Result is upper-case and not blank-padded (NUM_processor converts as needed)
+ * If input is out-of-range, produce '###############'
+ */
static char *
int_to_roman(int number)
{
@@ -5201,32 +5206,42 @@ int_to_roman(int number)
result = (char *) palloc(16);
*result = '\0';
+ /*
+ * This range limit is the same as in Oracle(TM). The difficulty with
+ * handling 4000 or more is that we'd need to use more than 3 "M"'s, and
+ * more than 3 of the same digit isn't considered a valid Roman string.
+ */
if (number > 3999 || number < 1)
{
fill_str(result, '#', 15);
return result;
}
+
+ /* Convert to decimal, then examine each digit */
len = snprintf(numstr, sizeof(numstr), "%d", number);
+ Assert(len > 0 && len <= 4);
for (p = numstr; *p != '\0'; p++, --len)
{
num = *p - ('0' + 1);
if (num < 0)
- continue;
-
- if (len > 3)
+ continue; /* ignore zeroes */
+ /* switch on current column position */
+ switch (len)
{
- while (num-- != -1)
- strcat(result, "M");
- }
- else
- {
- if (len == 3)
+ case 4:
+ while (num-- >= 0)
+ strcat(result, "M");
+ break;
+ case 3:
strcat(result, rm100[num]);
- else if (len == 2)
+ break;
+ case 2:
strcat(result, rm10[num]);
- else if (len == 1)
+ break;
+ case 1:
strcat(result, rm1[num]);
+ break;
}
}
return result;
@@ -6367,7 +6382,6 @@ numeric_to_char(PG_FUNCTION_ARGS)
char *numstr,
*orgnum,
*p;
- Numeric x;
NUM_TOCHAR_prepare;
@@ -6376,12 +6390,15 @@ numeric_to_char(PG_FUNCTION_ARGS)
*/
if (IS_ROMAN(&Num))
{
- x = DatumGetNumeric(DirectFunctionCall2(numeric_round,
- NumericGetDatum(value),
- Int32GetDatum(0)));
- numstr =
- int_to_roman(DatumGetInt32(DirectFunctionCall1(numeric_int4,
- NumericGetDatum(x))));
+ int32 intvalue;
+ bool err;
+
+ /* Round and convert to int */
+ intvalue = numeric_int4_opt_error(value, &err);
+ /* On overflow, just use PG_INT32_MAX; int_to_roman will cope */
+ if (err)
+ intvalue = PG_INT32_MAX;
+ numstr = int_to_roman(intvalue);
}
else if (IS_EEEE(&Num))
{
@@ -6421,6 +6438,7 @@ numeric_to_char(PG_FUNCTION_ARGS)
{
int numstr_pre_len;
Numeric val = value;
+ Numeric x;
if (IS_MULTI(&Num))
{
@@ -6589,12 +6607,18 @@ int8_to_char(PG_FUNCTION_ARGS)
NUM_TOCHAR_prepare;
/*
- * On DateType depend part (int32)
+ * On DateType depend part (int64)
*/
if (IS_ROMAN(&Num))
{
- /* Currently don't support int8 conversion to roman... */
- numstr = int_to_roman(DatumGetInt32(DirectFunctionCall1(int84, Int64GetDatum(value))));
+ int32 intvalue;
+
+ /* On overflow, just use PG_INT32_MAX; int_to_roman will cope */
+ if (value <= PG_INT32_MAX && value >= PG_INT32_MIN)
+ intvalue = (int32) value;
+ else
+ intvalue = PG_INT32_MAX;
+ numstr = int_to_roman(intvalue);
}
else if (IS_EEEE(&Num))
{
@@ -6695,7 +6719,18 @@ float4_to_char(PG_FUNCTION_ARGS)
NUM_TOCHAR_prepare;
if (IS_ROMAN(&Num))
- numstr = int_to_roman((int) rint(value));
+ {
+ int32 intvalue;
+
+ /* See notes in ftoi4() */
+ value = rint(value);
+ /* On overflow, just use PG_INT32_MAX; int_to_roman will cope */
+ if (!isnan(value) && FLOAT4_FITS_IN_INT32(value))
+ intvalue = (int32) value;
+ else
+ intvalue = PG_INT32_MAX;
+ numstr = int_to_roman(intvalue);
+ }
else if (IS_EEEE(&Num))
{
if (isnan(value) || isinf(value))
@@ -6797,7 +6832,18 @@ float8_to_char(PG_FUNCTION_ARGS)
NUM_TOCHAR_prepare;
if (IS_ROMAN(&Num))
- numstr = int_to_roman((int) rint(value));
+ {
+ int32 intvalue;
+
+ /* See notes in dtoi4() */
+ value = rint(value);
+ /* On overflow, just use PG_INT32_MAX; int_to_roman will cope */
+ if (!isnan(value) && FLOAT8_FITS_IN_INT32(value))
+ intvalue = (int32) value;
+ else
+ intvalue = PG_INT32_MAX;
+ numstr = int_to_roman(intvalue);
+ }
else if (IS_EEEE(&Num))
{
if (isnan(value) || isinf(value))