mirror of
https://github.com/postgres/postgres.git
synced 2025-04-22 23:02:54 +03:00
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
This commit is contained in:
parent
e3a92ab070
commit
147bbc90f7
@ -8739,6 +8739,8 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
|
|||||||
<replaceable>n</replaceable> is the number of digits following
|
<replaceable>n</replaceable> is the number of digits following
|
||||||
<literal>V</literal>. <literal>V</literal> with
|
<literal>V</literal>. <literal>V</literal> with
|
||||||
<function>to_number</function> divides in a similar manner.
|
<function>to_number</function> divides in a similar manner.
|
||||||
|
The <literal>V</literal> can be thought of as marking the position
|
||||||
|
of an implicit decimal point in the input or output string.
|
||||||
<function>to_char</function> and <function>to_number</function>
|
<function>to_char</function> and <function>to_number</function>
|
||||||
do not support the use of
|
do not support the use of
|
||||||
<literal>V</literal> combined with a decimal point
|
<literal>V</literal> combined with a decimal point
|
||||||
|
@ -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 *
|
static char *
|
||||||
int_to_roman(int number)
|
int_to_roman(int number)
|
||||||
{
|
{
|
||||||
@ -5201,32 +5206,42 @@ int_to_roman(int number)
|
|||||||
result = (char *) palloc(16);
|
result = (char *) palloc(16);
|
||||||
*result = '\0';
|
*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)
|
if (number > 3999 || number < 1)
|
||||||
{
|
{
|
||||||
fill_str(result, '#', 15);
|
fill_str(result, '#', 15);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Convert to decimal, then examine each digit */
|
||||||
len = snprintf(numstr, sizeof(numstr), "%d", number);
|
len = snprintf(numstr, sizeof(numstr), "%d", number);
|
||||||
|
Assert(len > 0 && len <= 4);
|
||||||
|
|
||||||
for (p = numstr; *p != '\0'; p++, --len)
|
for (p = numstr; *p != '\0'; p++, --len)
|
||||||
{
|
{
|
||||||
num = *p - ('0' + 1);
|
num = *p - ('0' + 1);
|
||||||
if (num < 0)
|
if (num < 0)
|
||||||
continue;
|
continue; /* ignore zeroes */
|
||||||
|
/* switch on current column position */
|
||||||
if (len > 3)
|
switch (len)
|
||||||
{
|
{
|
||||||
while (num-- != -1)
|
case 4:
|
||||||
strcat(result, "M");
|
while (num-- >= 0)
|
||||||
}
|
strcat(result, "M");
|
||||||
else
|
break;
|
||||||
{
|
case 3:
|
||||||
if (len == 3)
|
|
||||||
strcat(result, rm100[num]);
|
strcat(result, rm100[num]);
|
||||||
else if (len == 2)
|
break;
|
||||||
|
case 2:
|
||||||
strcat(result, rm10[num]);
|
strcat(result, rm10[num]);
|
||||||
else if (len == 1)
|
break;
|
||||||
|
case 1:
|
||||||
strcat(result, rm1[num]);
|
strcat(result, rm1[num]);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@ -6367,7 +6382,6 @@ numeric_to_char(PG_FUNCTION_ARGS)
|
|||||||
char *numstr,
|
char *numstr,
|
||||||
*orgnum,
|
*orgnum,
|
||||||
*p;
|
*p;
|
||||||
Numeric x;
|
|
||||||
|
|
||||||
NUM_TOCHAR_prepare;
|
NUM_TOCHAR_prepare;
|
||||||
|
|
||||||
@ -6376,12 +6390,15 @@ numeric_to_char(PG_FUNCTION_ARGS)
|
|||||||
*/
|
*/
|
||||||
if (IS_ROMAN(&Num))
|
if (IS_ROMAN(&Num))
|
||||||
{
|
{
|
||||||
x = DatumGetNumeric(DirectFunctionCall2(numeric_round,
|
int32 intvalue;
|
||||||
NumericGetDatum(value),
|
bool err;
|
||||||
Int32GetDatum(0)));
|
|
||||||
numstr =
|
/* Round and convert to int */
|
||||||
int_to_roman(DatumGetInt32(DirectFunctionCall1(numeric_int4,
|
intvalue = numeric_int4_opt_error(value, &err);
|
||||||
NumericGetDatum(x))));
|
/* 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))
|
else if (IS_EEEE(&Num))
|
||||||
{
|
{
|
||||||
@ -6421,6 +6438,7 @@ numeric_to_char(PG_FUNCTION_ARGS)
|
|||||||
{
|
{
|
||||||
int numstr_pre_len;
|
int numstr_pre_len;
|
||||||
Numeric val = value;
|
Numeric val = value;
|
||||||
|
Numeric x;
|
||||||
|
|
||||||
if (IS_MULTI(&Num))
|
if (IS_MULTI(&Num))
|
||||||
{
|
{
|
||||||
@ -6589,12 +6607,18 @@ int8_to_char(PG_FUNCTION_ARGS)
|
|||||||
NUM_TOCHAR_prepare;
|
NUM_TOCHAR_prepare;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* On DateType depend part (int32)
|
* On DateType depend part (int64)
|
||||||
*/
|
*/
|
||||||
if (IS_ROMAN(&Num))
|
if (IS_ROMAN(&Num))
|
||||||
{
|
{
|
||||||
/* Currently don't support int8 conversion to roman... */
|
int32 intvalue;
|
||||||
numstr = int_to_roman(DatumGetInt32(DirectFunctionCall1(int84, Int64GetDatum(value))));
|
|
||||||
|
/* 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))
|
else if (IS_EEEE(&Num))
|
||||||
{
|
{
|
||||||
@ -6695,7 +6719,18 @@ float4_to_char(PG_FUNCTION_ARGS)
|
|||||||
NUM_TOCHAR_prepare;
|
NUM_TOCHAR_prepare;
|
||||||
|
|
||||||
if (IS_ROMAN(&Num))
|
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))
|
else if (IS_EEEE(&Num))
|
||||||
{
|
{
|
||||||
if (isnan(value) || isinf(value))
|
if (isnan(value) || isinf(value))
|
||||||
@ -6797,7 +6832,18 @@ float8_to_char(PG_FUNCTION_ARGS)
|
|||||||
NUM_TOCHAR_prepare;
|
NUM_TOCHAR_prepare;
|
||||||
|
|
||||||
if (IS_ROMAN(&Num))
|
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))
|
else if (IS_EEEE(&Num))
|
||||||
{
|
{
|
||||||
if (isnan(value) || isinf(value))
|
if (isnan(value) || isinf(value))
|
||||||
|
@ -530,6 +530,16 @@ SELECT to_char(q2, 'MI9999999999999999') FROM INT8_TBL;
|
|||||||
-4567890123456789
|
-4567890123456789
|
||||||
(5 rows)
|
(5 rows)
|
||||||
|
|
||||||
|
SELECT to_char(q2, '9999999999999999PL') FROM INT8_TBL;
|
||||||
|
to_char
|
||||||
|
--------------------
|
||||||
|
456+
|
||||||
|
4567890123456789+
|
||||||
|
123+
|
||||||
|
4567890123456789+
|
||||||
|
-4567890123456789
|
||||||
|
(5 rows)
|
||||||
|
|
||||||
SELECT to_char(q2, 'FMS9999999999999999') FROM INT8_TBL;
|
SELECT to_char(q2, 'FMS9999999999999999') FROM INT8_TBL;
|
||||||
to_char
|
to_char
|
||||||
-------------------
|
-------------------
|
||||||
@ -650,6 +660,46 @@ SELECT to_char(q2, '999999SG9999999999') FROM INT8_TBL;
|
|||||||
456789-0123456789
|
456789-0123456789
|
||||||
(5 rows)
|
(5 rows)
|
||||||
|
|
||||||
|
SELECT to_char(q2, 'FMRN') FROM INT8_TBL;
|
||||||
|
to_char
|
||||||
|
-----------------
|
||||||
|
CDLVI
|
||||||
|
###############
|
||||||
|
CXXIII
|
||||||
|
###############
|
||||||
|
###############
|
||||||
|
(5 rows)
|
||||||
|
|
||||||
|
SELECT to_char(1234, '9.99EEEE');
|
||||||
|
to_char
|
||||||
|
-----------
|
||||||
|
1.23e+03
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT to_char(1234::int8, '9.99eeee');
|
||||||
|
to_char
|
||||||
|
-----------
|
||||||
|
1.23e+03
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT to_char(-1234::int8, '9.99eeee');
|
||||||
|
to_char
|
||||||
|
-----------
|
||||||
|
-1.23e+03
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT to_char(1234, '99999V99');
|
||||||
|
to_char
|
||||||
|
----------
|
||||||
|
123400
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT to_char(1234::int8, '99999V99');
|
||||||
|
to_char
|
||||||
|
----------
|
||||||
|
123400
|
||||||
|
(1 row)
|
||||||
|
|
||||||
-- check min/max values and overflow behavior
|
-- check min/max values and overflow behavior
|
||||||
select '-9223372036854775808'::int8;
|
select '-9223372036854775808'::int8;
|
||||||
int8
|
int8
|
||||||
|
@ -1991,6 +1991,21 @@ SELECT to_char(val, '9.999EEEE') FROM num_data;
|
|||||||
-2.493e+07
|
-2.493e+07
|
||||||
(10 rows)
|
(10 rows)
|
||||||
|
|
||||||
|
SELECT to_char(val, 'FMRN') FROM num_data;
|
||||||
|
to_char
|
||||||
|
-----------------
|
||||||
|
###############
|
||||||
|
###############
|
||||||
|
###############
|
||||||
|
IV
|
||||||
|
###############
|
||||||
|
###############
|
||||||
|
###############
|
||||||
|
###############
|
||||||
|
###############
|
||||||
|
###############
|
||||||
|
(10 rows)
|
||||||
|
|
||||||
WITH v(val) AS
|
WITH v(val) AS
|
||||||
(VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan'))
|
(VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan'))
|
||||||
SELECT val,
|
SELECT val,
|
||||||
@ -2101,6 +2116,72 @@ SELECT to_char('12345678901'::float8, 'FM9999999999D9999900000000000000000');
|
|||||||
##########.####
|
##########.####
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
SELECT to_char('100'::numeric, 'rn');
|
||||||
|
to_char
|
||||||
|
-----------------
|
||||||
|
c
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT to_char('1234'::numeric, 'rn');
|
||||||
|
to_char
|
||||||
|
-----------------
|
||||||
|
mccxxxiv
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT to_char('1235'::float4, 'rn');
|
||||||
|
to_char
|
||||||
|
-----------------
|
||||||
|
mccxxxv
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT to_char('1236'::float8, 'rn');
|
||||||
|
to_char
|
||||||
|
-----------------
|
||||||
|
mccxxxvi
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT to_char('1237'::float8, 'fmrn');
|
||||||
|
to_char
|
||||||
|
-----------
|
||||||
|
mccxxxvii
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT to_char('100e9'::numeric, 'RN');
|
||||||
|
to_char
|
||||||
|
-----------------
|
||||||
|
###############
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT to_char('100e9'::float4, 'RN');
|
||||||
|
to_char
|
||||||
|
-----------------
|
||||||
|
###############
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT to_char('100e9'::float8, 'RN');
|
||||||
|
to_char
|
||||||
|
-----------------
|
||||||
|
###############
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT to_char(1234.56::numeric, '99999V99');
|
||||||
|
to_char
|
||||||
|
----------
|
||||||
|
123456
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT to_char(1234.56::float4, '99999V99');
|
||||||
|
to_char
|
||||||
|
----------
|
||||||
|
123456
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT to_char(1234.56::float8, '99999V99');
|
||||||
|
to_char
|
||||||
|
----------
|
||||||
|
123456
|
||||||
|
(1 row)
|
||||||
|
|
||||||
-- Check parsing of literal text in a format string
|
-- Check parsing of literal text in a format string
|
||||||
SELECT to_char('100'::numeric, 'foo999');
|
SELECT to_char('100'::numeric, 'foo999');
|
||||||
to_char
|
to_char
|
||||||
@ -2297,6 +2378,12 @@ SELECT to_number('42nd', '99th');
|
|||||||
42
|
42
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
|
SELECT to_number('123456', '99999V99');
|
||||||
|
to_number
|
||||||
|
-------------------------
|
||||||
|
1234.560000000000000000
|
||||||
|
(1 row)
|
||||||
|
|
||||||
RESET lc_numeric;
|
RESET lc_numeric;
|
||||||
--
|
--
|
||||||
-- Input syntax
|
-- Input syntax
|
||||||
|
@ -110,6 +110,7 @@ SELECT to_char( (q1 * -1), '9999999999999999S'), to_char( (q2 * -1), 'S999999999
|
|||||||
FROM INT8_TBL;
|
FROM INT8_TBL;
|
||||||
|
|
||||||
SELECT to_char(q2, 'MI9999999999999999') FROM INT8_TBL;
|
SELECT to_char(q2, 'MI9999999999999999') FROM INT8_TBL;
|
||||||
|
SELECT to_char(q2, '9999999999999999PL') FROM INT8_TBL;
|
||||||
SELECT to_char(q2, 'FMS9999999999999999') FROM INT8_TBL;
|
SELECT to_char(q2, 'FMS9999999999999999') FROM INT8_TBL;
|
||||||
SELECT to_char(q2, 'FM9999999999999999THPR') FROM INT8_TBL;
|
SELECT to_char(q2, 'FM9999999999999999THPR') FROM INT8_TBL;
|
||||||
SELECT to_char(q2, 'SG9999999999999999th') FROM INT8_TBL;
|
SELECT to_char(q2, 'SG9999999999999999th') FROM INT8_TBL;
|
||||||
@ -122,6 +123,13 @@ SELECT to_char(q2, 'FM9999999999999999.999') FROM INT8_TBL;
|
|||||||
SELECT to_char(q2, 'S 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 . 9 9 9') FROM INT8_TBL;
|
SELECT to_char(q2, 'S 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 . 9 9 9') FROM INT8_TBL;
|
||||||
SELECT to_char(q2, E'99999 "text" 9999 "9999" 999 "\\"text between quote marks\\"" 9999') FROM INT8_TBL;
|
SELECT to_char(q2, E'99999 "text" 9999 "9999" 999 "\\"text between quote marks\\"" 9999') FROM INT8_TBL;
|
||||||
SELECT to_char(q2, '999999SG9999999999') FROM INT8_TBL;
|
SELECT to_char(q2, '999999SG9999999999') FROM INT8_TBL;
|
||||||
|
SELECT to_char(q2, 'FMRN') FROM INT8_TBL;
|
||||||
|
|
||||||
|
SELECT to_char(1234, '9.99EEEE');
|
||||||
|
SELECT to_char(1234::int8, '9.99eeee');
|
||||||
|
SELECT to_char(-1234::int8, '9.99eeee');
|
||||||
|
SELECT to_char(1234, '99999V99');
|
||||||
|
SELECT to_char(1234::int8, '99999V99');
|
||||||
|
|
||||||
-- check min/max values and overflow behavior
|
-- check min/max values and overflow behavior
|
||||||
|
|
||||||
|
@ -996,6 +996,7 @@ SELECT to_char(val, E'99999 "text" 9999 "9999" 999 "\\"text between quote marks\
|
|||||||
SELECT to_char(val, '999999SG9999999999') FROM num_data;
|
SELECT to_char(val, '999999SG9999999999') FROM num_data;
|
||||||
SELECT to_char(val, 'FM9999999999999999.999999999999999') FROM num_data;
|
SELECT to_char(val, 'FM9999999999999999.999999999999999') FROM num_data;
|
||||||
SELECT to_char(val, '9.999EEEE') FROM num_data;
|
SELECT to_char(val, '9.999EEEE') FROM num_data;
|
||||||
|
SELECT to_char(val, 'FMRN') FROM num_data;
|
||||||
|
|
||||||
WITH v(val) AS
|
WITH v(val) AS
|
||||||
(VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan'))
|
(VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan'))
|
||||||
@ -1033,6 +1034,19 @@ SELECT to_char('100'::numeric, 'FM999.');
|
|||||||
SELECT to_char('100'::numeric, 'FM999');
|
SELECT to_char('100'::numeric, 'FM999');
|
||||||
SELECT to_char('12345678901'::float8, 'FM9999999999D9999900000000000000000');
|
SELECT to_char('12345678901'::float8, 'FM9999999999D9999900000000000000000');
|
||||||
|
|
||||||
|
SELECT to_char('100'::numeric, 'rn');
|
||||||
|
SELECT to_char('1234'::numeric, 'rn');
|
||||||
|
SELECT to_char('1235'::float4, 'rn');
|
||||||
|
SELECT to_char('1236'::float8, 'rn');
|
||||||
|
SELECT to_char('1237'::float8, 'fmrn');
|
||||||
|
SELECT to_char('100e9'::numeric, 'RN');
|
||||||
|
SELECT to_char('100e9'::float4, 'RN');
|
||||||
|
SELECT to_char('100e9'::float8, 'RN');
|
||||||
|
|
||||||
|
SELECT to_char(1234.56::numeric, '99999V99');
|
||||||
|
SELECT to_char(1234.56::float4, '99999V99');
|
||||||
|
SELECT to_char(1234.56::float8, '99999V99');
|
||||||
|
|
||||||
-- Check parsing of literal text in a format string
|
-- Check parsing of literal text in a format string
|
||||||
SELECT to_char('100'::numeric, 'foo999');
|
SELECT to_char('100'::numeric, 'foo999');
|
||||||
SELECT to_char('100'::numeric, 'f\oo999');
|
SELECT to_char('100'::numeric, 'f\oo999');
|
||||||
@ -1070,6 +1084,7 @@ SELECT to_number('$1,234.56','L99,999.99');
|
|||||||
SELECT to_number('1234.56','L99,999.99');
|
SELECT to_number('1234.56','L99,999.99');
|
||||||
SELECT to_number('1,234.56','L99,999.99');
|
SELECT to_number('1,234.56','L99,999.99');
|
||||||
SELECT to_number('42nd', '99th');
|
SELECT to_number('42nd', '99th');
|
||||||
|
SELECT to_number('123456', '99999V99');
|
||||||
RESET lc_numeric;
|
RESET lc_numeric;
|
||||||
|
|
||||||
--
|
--
|
||||||
|
Loading…
x
Reference in New Issue
Block a user