From cc0d90b73b2e6dd2f301d46818a7265742c41a14 Mon Sep 17 00:00:00 2001 From: Bruce Momjian Date: Sat, 21 Mar 2015 21:43:15 -0400 Subject: [PATCH] to_char(float4/8): zero pad to specified length Previously, zero padding was limited to the internal length, rather than the specified length. This allows it to match to_char(int/numeric), which always padded to the specified length. Regression tests added. BACKWARD INCOMPATIBILITY --- src/backend/utils/adt/formatting.c | 112 +++++++++++++++++--------- src/test/regress/expected/numeric.out | 70 ++++++++++++++++ src/test/regress/expected/window.out | 2 +- src/test/regress/sql/numeric.sql | 17 ++++ 4 files changed, 162 insertions(+), 39 deletions(-) diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index 40a353f513..25b247ee78 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -113,13 +113,6 @@ #define DCH_MAX_ITEM_SIZ 12 /* max localized day name */ #define NUM_MAX_ITEM_SIZ 8 /* roman number (RN has 15 chars) */ -/* ---------- - * More is in float.c - * ---------- - */ -#define MAXFLOATWIDTH 60 -#define MAXDOUBLEWIDTH 500 - /* ---------- * Format parser structs @@ -5214,8 +5207,7 @@ int4_to_char(PG_FUNCTION_ARGS) /* we can do it easily because float8 won't lose any precision */ float8 val = (float8) value; - orgnum = (char *) palloc(MAXDOUBLEWIDTH + 1); - snprintf(orgnum, MAXDOUBLEWIDTH + 1, "%+.*e", Num.post, val); + orgnum = psprintf("%+.*e", Num.post, val); /* * Swap a leading positive sign for a space. @@ -5414,7 +5406,6 @@ float4_to_char(PG_FUNCTION_ARGS) numstr = orgnum = int_to_roman((int) rint(value)); else if (IS_EEEE(&Num)) { - numstr = orgnum = (char *) palloc(MAXDOUBLEWIDTH + 1); if (isnan(value) || is_infinite(value)) { /* @@ -5428,15 +5419,29 @@ float4_to_char(PG_FUNCTION_ARGS) } else { - snprintf(orgnum, MAXDOUBLEWIDTH + 1, "%+.*e", Num.post, value); + numstr = psprintf("%+.*e", Num.post, value); + + /* prevent the display of imprecise/junk digits */ + if (Num.pre + Num.post > FLT_DIG) + { + int digits = 0; + char *numstr_p; + + for (numstr_p = numstr; *numstr_p && *numstr_p != 'e'; numstr_p++) + { + if (isdigit(*numstr_p)) + { + if (++digits > FLT_DIG) + *numstr_p = '0'; + } + } + } /* * Swap a leading positive sign for a space. */ - if (*orgnum == '+') - *orgnum = ' '; - - numstr = orgnum; + if (*numstr == '+') + *numstr = ' '; } } else @@ -5452,16 +5457,24 @@ float4_to_char(PG_FUNCTION_ARGS) Num.pre += Num.multi; } - orgnum = (char *) palloc(MAXFLOATWIDTH + 1); - snprintf(orgnum, MAXFLOATWIDTH + 1, "%.0f", fabs(val)); - numstr_pre_len = strlen(orgnum); + /* let psprintf() do the rounding */ + orgnum = psprintf("%.*f", Num.post, val); - /* adjust post digits to fit max float digits */ - if (numstr_pre_len >= FLT_DIG) - Num.post = 0; - else if (numstr_pre_len + Num.post > FLT_DIG) - Num.post = FLT_DIG - numstr_pre_len; - snprintf(orgnum, MAXFLOATWIDTH + 1, "%.*f", Num.post, val); + /* prevent the display of imprecise/junk digits */ + if (Num.pre + Num.post > FLT_DIG) + { + int digits = 0; + char *orgnum_p; + + for (orgnum_p = orgnum; *orgnum_p; orgnum_p++) + { + if (isdigit(*orgnum_p)) + { + if (++digits > FLT_DIG) + *orgnum_p = '0'; + } + } + } if (*orgnum == '-') { /* < 0 */ @@ -5520,7 +5533,6 @@ float8_to_char(PG_FUNCTION_ARGS) numstr = orgnum = int_to_roman((int) rint(value)); else if (IS_EEEE(&Num)) { - numstr = orgnum = (char *) palloc(MAXDOUBLEWIDTH + 1); if (isnan(value) || is_infinite(value)) { /* @@ -5534,15 +5546,29 @@ float8_to_char(PG_FUNCTION_ARGS) } else { - snprintf(orgnum, MAXDOUBLEWIDTH + 1, "%+.*e", Num.post, value); + numstr = psprintf("%+.*e", Num.post, value); + + /* prevent the display of imprecise/junk digits */ + if (Num.pre + Num.post > DBL_DIG) + { + int digits = 0; + char *numstr_p; + + for (numstr_p = numstr; *numstr_p && *numstr_p != 'e'; numstr_p++) + { + if (isdigit(*numstr_p)) + { + if (++digits > DBL_DIG) + *numstr_p = '0'; + } + } + } /* * Swap a leading positive sign for a space. */ - if (*orgnum == '+') - *orgnum = ' '; - - numstr = orgnum; + if (*numstr == '+') + *numstr = ' '; } } else @@ -5557,15 +5583,25 @@ float8_to_char(PG_FUNCTION_ARGS) val = value * multi; Num.pre += Num.multi; } - orgnum = (char *) palloc(MAXDOUBLEWIDTH + 1); - numstr_pre_len = snprintf(orgnum, MAXDOUBLEWIDTH + 1, "%.0f", fabs(val)); - /* adjust post digits to fit max double digits */ - if (numstr_pre_len >= DBL_DIG) - Num.post = 0; - else if (numstr_pre_len + Num.post > DBL_DIG) - Num.post = DBL_DIG - numstr_pre_len; - snprintf(orgnum, MAXDOUBLEWIDTH + 1, "%.*f", Num.post, val); + /* let psprintf() do the rounding */ + orgnum = psprintf("%.*f", Num.post, val); + + /* prevent the display of imprecise/junk digits */ + if (Num.pre + Num.post > DBL_DIG) + { + int digits = 0; + char *orgnum_p; + + for (orgnum_p = orgnum; *orgnum_p; orgnum_p++) + { + if (isdigit(*orgnum_p)) + { + if (++digits > DBL_DIG) + *orgnum_p = '0'; + } + } + } if (*orgnum == '-') { /* < 0 */ diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out index 9d6814564d..46b0c72b6a 100644 --- a/src/test/regress/expected/numeric.out +++ b/src/test/regress/expected/numeric.out @@ -1499,3 +1499,73 @@ select * from generate_series(1::numeric, 3::numeric) i, generate_series(1,5,i) 3 | 4 (10 rows) +-- +-- Test code path for high-precision output +-- +SELECT to_char(float8 '99999999999', '9999999999999999D99999999'); + to_char +---------------------------- + 99999999999.00000000 +(1 row) + +SELECT to_char(float8 '99999999999', '9999999999999999D' || repeat('9', 1000)); + to_char +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + 99999999999.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +(1 row) + +SELECT to_char(float8 '1e9','999999999999999999999D9'); + to_char +-------------------------- + 1000000000.0 +(1 row) + +SELECT to_char(float8 '1e20','999999999999999999999D9'); + to_char +-------------------------- + 100000000000000000000.0 +(1 row) + +SELECT to_char(1e20, '999999999999999999999D9'); + to_char +-------------------------- + 100000000000000000000.0 +(1 row) + +SELECT to_char(float8 '1.123456789123456789', '9.' || repeat('9', 55)); + to_char +------------------------------------------------------------ + 1.1234567891234500000000000000000000000000000000000000000 +(1 row) + +SELECT to_char(float8 '1999999999999999999999999999999999999999999999.123456789123456789', + repeat('9', 50) || '.' || repeat('9', 50)); + to_char +-------------------------------------------------------------------------------------------------------- + 1999999999999990000000000000000000000000000000.00000000000000000000000000000000000000000000000000 +(1 row) + +SELECT to_char(float8 '0.1', '9D' || repeat('9', 1000)); + to_char +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + .1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +(1 row) + +SELECT to_char(int4 '1', '9D' || repeat('9', 1000) || 'EEEE'); + to_char +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+00 +(1 row) + +SELECT to_char(float4 '1', '9D' || repeat('9', 1000) || 'EEEE'); + to_char +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+00 +(1 row) + +SELECT to_char(float8 '1', '9D' || repeat('9', 1000) || 'EEEE'); + to_char +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+00 +(1 row) + diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out index 19f909f3d1..79e65f6e6b 100644 --- a/src/test/regress/expected/window.out +++ b/src/test/regress/expected/window.out @@ -1806,7 +1806,7 @@ SELECT to_char(SUM(n::float8) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND 1 FO FROM (VALUES(1,1e20),(2,1)) n(i,n); to_char -------------------------- - 100000000000000000000 + 100000000000000000000.0 1.0 (2 rows) diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sql index 1633e4c375..a6301eab0a 100644 --- a/src/test/regress/sql/numeric.sql +++ b/src/test/regress/sql/numeric.sql @@ -858,3 +858,20 @@ select (i / (10::numeric ^ 131071))::numeric(1,0) select * from generate_series(1::numeric, 3::numeric) i, generate_series(i,3) j; select * from generate_series(1::numeric, 3::numeric) i, generate_series(1,i) j; select * from generate_series(1::numeric, 3::numeric) i, generate_series(1,5,i) j; + +-- +-- Test code path for high-precision output +-- + +SELECT to_char(float8 '99999999999', '9999999999999999D99999999'); +SELECT to_char(float8 '99999999999', '9999999999999999D' || repeat('9', 1000)); +SELECT to_char(float8 '1e9','999999999999999999999D9'); +SELECT to_char(float8 '1e20','999999999999999999999D9'); +SELECT to_char(1e20, '999999999999999999999D9'); +SELECT to_char(float8 '1.123456789123456789', '9.' || repeat('9', 55)); +SELECT to_char(float8 '1999999999999999999999999999999999999999999999.123456789123456789', + repeat('9', 50) || '.' || repeat('9', 50)); +SELECT to_char(float8 '0.1', '9D' || repeat('9', 1000)); +SELECT to_char(int4 '1', '9D' || repeat('9', 1000) || 'EEEE'); +SELECT to_char(float4 '1', '9D' || repeat('9', 1000) || 'EEEE'); +SELECT to_char(float8 '1', '9D' || repeat('9', 1000) || 'EEEE');