On Tue, Mar 24, 2015 at 09:47:56AM -0400, Noah Misch wrote: > On Sun, Mar 22, 2015 at 10:53:12PM -0400, Bruce Momjian wrote: > > On Sun, Mar 22, 2015 at 04:41:19PM -0400, Noah Misch wrote: > > > On Wed, Mar 18, 2015 at 05:52:44PM -0400, Bruce Momjian wrote: > > > > This "junk" digit zeroing matches the Oracle behavior: > > > > > > > > SELECT to_char(1.123456789123456789123456789d, > > > > '9.9999999999999999999999999999999999999') as x from dual; > > > > ------ > > > > 1.1234567891234568000000000000000000000 > > > > > > > > Our output with the patch would be: > > > > > > > > SELECT to_char(float8 '1.123456789123456789123456789', > > > > '9.9999999999999999999999999999999999999'); > > > > ------ > > > > 1.1234567891234500000000000000000000000 > > > > These outputs show Oracle treating 17 digits as significant while > > > PostgreSQL > > > treats 15 digits as significant. Should we match Oracle in this respect > > > while > > > we're breaking compatibility anyway? I tend to think yes. > > > > Uh, I am hesistant to adjust our precision to match Oracle as I don't > > know what they are using internally. > > http://sqlfiddle.com/#!4/8b4cf/5 strongly implies 17 significant digits for > float8 and 9 digits for float4.
I was able to get proper rounding with the attached patch. test=> SELECT to_char(float8 '1.123456789123456789123456789', '9.9999999999999999999999999999999999999'); to_char ------------------------------------------ 1.1234567891234600000000000000000000000 (1 row) Handling rounding for exponent-format values turned out to be simple. What has me stuck now is how to do rounding in the non-decimal part of the number, e.g. test=> SELECT to_char(float4 '15555555555555.912345678912345678900000000000000000000000', repeat('9', 50) || '.' || repeat('9', 50)); to_char -------------------------------------------------------------------------------------------------------- 15555555753984.00000000000000000000000000000000000000000000000000 (1 row) This should return something like 15555600000000.000... (per Oracle output at the URL above, float4 has 6 significant digits on my compiler) but I can't seem to figure how to get printf() to round non-fractional parts. I am afraid the only solution is to use printf's %e format and place the decimal point myself. The fact I still don't have a complete solution suggests this is 9.6 material but I still want to work on it so it is ready. -- Bruce Momjian <br...@momjian.us> http://momjian.us EnterpriseDB http://enterprisedb.com + Everyone has their own god. +
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c new file mode 100644 index 40a353f..2b5a440 *** a/src/backend/utils/adt/formatting.c --- b/src/backend/utils/adt/formatting.c *************** *** 113,125 **** #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 --- 113,118 ---- *************** static DCHCacheEntry *DCH_cache_getnew(c *** 989,994 **** --- 982,988 ---- static NUMCacheEntry *NUM_cache_search(char *str); static NUMCacheEntry *NUM_cache_getnew(char *str); static void NUM_cache_remove(NUMCacheEntry *ent); + static char *add_zero_padding(char *num_str, int pad_digits); /* ---------- *************** do { \ *** 5016,5021 **** --- 5010,5056 ---- SET_VARSIZE(result, len + VARHDRSZ); \ } while (0) + /* + * add_zero_padding + * + * Some sprintf() implementations have a 512-digit precision limit, and we + * need sprintf() to round to the internal precision, so this function adds + * zero padding between the mantissa and exponent of an exponential-format + * number, or after the supplied string for non-exponent strings. + */ + static char * + add_zero_padding(char *num_str, int pad_digits) + { + /* one for decimal point, one for trailing null byte */ + char *out = palloc(strlen(num_str) + pad_digits + 1 + 1), *out_p = out; + char *num_str_p = num_str; + bool found_decimal = false; + + /* copy the number before 'e', or the entire string if no 'e' */ + while (*num_str_p && *num_str_p != 'e' && *num_str_p != 'E') + { + if (*num_str_p == '.') + found_decimal = true; + *(out_p++) = *(num_str_p++); + } + + if (!found_decimal) + *(out_p++) = '.'; + + /* add zero pad digits */ + while (pad_digits-- > 0) + *(out_p++) = '0'; + + /* copy 'e' and everything after */ + while (*num_str_p) + *(out_p++) = *(num_str_p++); + + *(out_p++) = '\0'; + + pfree(num_str); + return out; + } + /* ------------------- * NUMERIC to_number() (convert string to numeric) * ------------------- *************** int4_to_char(PG_FUNCTION_ARGS) *** 5214,5221 **** /* 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); /* * Swap a leading positive sign for a space. --- 5249,5260 ---- /* we can do it easily because float8 won't lose any precision */ float8 val = (float8) value; ! /* Use '1' if there is any Num.post, so we get a decimal point */ ! orgnum = psprintf("%+.*e", Min(Num.post, 1), val); ! ! /* Add any additional zeros */ ! if (Num.post > 1) ! orgnum = add_zero_padding(orgnum, Num.post - 1); /* * Swap a leading positive sign for a space. *************** int4_to_char(PG_FUNCTION_ARGS) *** 5253,5265 **** /* post-decimal digits? Pad out with zeros. */ if (Num.post) ! { ! numstr = (char *) palloc(numstr_pre_len + Num.post + 2); ! strcpy(numstr, orgnum); ! *(numstr + numstr_pre_len) = '.'; ! memset(numstr + numstr_pre_len + 1, '0', Num.post); ! *(numstr + numstr_pre_len + Num.post + 1) = '\0'; ! } else numstr = orgnum; --- 5292,5298 ---- /* post-decimal digits? Pad out with zeros. */ if (Num.post) ! numstr = add_zero_padding(pstrdup(orgnum), Num.post); else numstr = orgnum; *************** int8_to_char(PG_FUNCTION_ARGS) *** 5363,5375 **** /* post-decimal digits? Pad out with zeros. */ if (Num.post) ! { ! numstr = (char *) palloc(numstr_pre_len + Num.post + 2); ! strcpy(numstr, orgnum); ! *(numstr + numstr_pre_len) = '.'; ! memset(numstr + numstr_pre_len + 1, '0', Num.post); ! *(numstr + numstr_pre_len + Num.post + 1) = '\0'; ! } else numstr = orgnum; --- 5396,5402 ---- /* post-decimal digits? Pad out with zeros. */ if (Num.post) ! numstr = add_zero_padding(pstrdup(orgnum), Num.post); else numstr = orgnum; *************** float4_to_char(PG_FUNCTION_ARGS) *** 5414,5420 **** 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)) { /* --- 5441,5446 ---- *************** float4_to_char(PG_FUNCTION_ARGS) *** 5428,5448 **** } else { ! snprintf(orgnum, MAXDOUBLEWIDTH + 1, "%+.*e", Num.post, value); /* * Swap a leading positive sign for a space. */ ! if (*orgnum == '+') ! *orgnum = ' '; ! ! numstr = orgnum; } } else { float4 val = value; ! int numstr_pre_len; if (IS_MULTI(&Num)) { --- 5454,5479 ---- } else { ! /* We already have one decimal digit before the decimal point. */ ! numstr = psprintf("%+.*e", ! Min(Num.post, FLT_DIG + extra_float_digits - 1), value); ! ! if (Num.post > FLT_DIG + extra_float_digits - 1) ! numstr = add_zero_padding(numstr, ! Num.post - FLT_DIG - extra_float_digits + 1); /* * Swap a leading positive sign for a space. */ ! if (*numstr == '+') ! *numstr = ' '; } } else { float4 val = value; ! int numstr_pre_len, val_exp = 0; ! char exp_buf[10]; if (IS_MULTI(&Num)) { *************** float4_to_char(PG_FUNCTION_ARGS) *** 5452,5467 **** Num.pre += Num.multi; } ! orgnum = (char *) palloc(MAXFLOATWIDTH + 1); ! snprintf(orgnum, MAXFLOATWIDTH + 1, "%.0f", fabs(val)); ! numstr_pre_len = strlen(orgnum); ! /* 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); if (*orgnum == '-') { /* < 0 */ --- 5483,5505 ---- Num.pre += Num.multi; } ! /* Find possible negative exponent */ ! snprintf(exp_buf, sizeof(exp_buf), "%.0e", val); ! if (strchr(exp_buf, 'e') != NULL) ! { ! val_exp = atoi(strchr(exp_buf, 'e') + 1); ! /* exp assumes one digit before the decimal point, so increment */ ! if (++val_exp > FLT_DIG + extra_float_digits) ! val_exp = FLT_DIG + extra_float_digits; ! } ! /* let psprintf() do the rounding */ ! orgnum = psprintf("%.*f", ! Min(Num.post, FLT_DIG + extra_float_digits - val_exp), val); ! ! if (Num.post > FLT_DIG + extra_float_digits - val_exp) ! orgnum = add_zero_padding(orgnum, ! Num.post - FLT_DIG - extra_float_digits + val_exp); if (*orgnum == '-') { /* < 0 */ *************** float8_to_char(PG_FUNCTION_ARGS) *** 5520,5526 **** 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)) { /* --- 5558,5563 ---- *************** float8_to_char(PG_FUNCTION_ARGS) *** 5534,5554 **** } else { ! snprintf(orgnum, MAXDOUBLEWIDTH + 1, "%+.*e", Num.post, value); /* * Swap a leading positive sign for a space. */ ! if (*orgnum == '+') ! *orgnum = ' '; ! ! numstr = orgnum; } } else { float8 val = value; ! int numstr_pre_len; if (IS_MULTI(&Num)) { --- 5571,5596 ---- } else { ! /* We already have one decimal digit before the decimal point. */ ! numstr = psprintf("%+.*e", ! Min(Num.post, DBL_DIG + extra_float_digits - 1), value); ! ! if (Num.post > DBL_DIG + extra_float_digits - 1) ! numstr = add_zero_padding(numstr, ! Num.post - DBL_DIG - extra_float_digits + 1); /* * Swap a leading positive sign for a space. */ ! if (*numstr == '+') ! *numstr = ' '; } } else { float8 val = value; ! int numstr_pre_len, val_exp = 0; ! char exp_buf[10]; if (IS_MULTI(&Num)) { *************** float8_to_char(PG_FUNCTION_ARGS) *** 5557,5571 **** 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); if (*orgnum == '-') { /* < 0 */ --- 5599,5622 ---- val = value * multi; Num.pre += Num.multi; } ! /* Find possible negative exponent */ ! snprintf(exp_buf, sizeof(exp_buf), "%.0e", val); ! if (strchr(exp_buf, 'e') != NULL) ! { ! val_exp = atoi(strchr(exp_buf, 'e') + 1); ! /* exp assumes one digit before the decimal point, so increment */ ! if (++val_exp > DBL_DIG + extra_float_digits) ! val_exp = DBL_DIG + extra_float_digits; ! } ! ! /* let psprintf() do the rounding */ ! orgnum = psprintf("%.*f", ! Min(Num.post, DBL_DIG + extra_float_digits - val_exp), val); ! ! if (Num.post > DBL_DIG + extra_float_digits - val_exp) ! orgnum = add_zero_padding(orgnum, ! Num.post - DBL_DIG - extra_float_digits + val_exp); if (*orgnum == '-') { /* < 0 */ diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out new file mode 100644 index 9d68145..0113522 *** a/src/test/regress/expected/numeric.out --- b/src/test/regress/expected/numeric.out *************** select * from generate_series(1::numeric *** 1499,1501 **** --- 1499,1571 ---- 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.1234567891234600000000000000000000000000000000000000000 + (1 row) + + SELECT to_char(float8 '1999999999999999999999999999999999999999999999.123456789123456789', + repeat('9', 50) || '.' || repeat('9', 50)); + to_char + -------------------------------------------------------------------------------------------------------- + 1999999999999999859514578049071102439861518336.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 new file mode 100644 index 19f909f..79e65f6 *** a/src/test/regress/expected/window.out --- b/src/test/regress/expected/window.out *************** SELECT to_char(SUM(n::float8) OVER (ORDE *** 1806,1812 **** FROM (VALUES(1,1e20),(2,1)) n(i,n); to_char -------------------------- ! 100000000000000000000 1.0 (2 rows) --- 1806,1812 ---- FROM (VALUES(1,1e20),(2,1)) n(i,n); to_char -------------------------- ! 100000000000000000000.0 1.0 (2 rows) diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sql new file mode 100644 index 1633e4c..a6301ea *** a/src/test/regress/sql/numeric.sql --- b/src/test/regress/sql/numeric.sql *************** select (i / (10::numeric ^ 131071))::num *** 858,860 **** --- 858,877 ---- 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');
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers