Skip to content

Commit e61fd4a

Browse files
committed
Support EEEE (scientific notation) in to_char().
Pavel Stehule, Brendan Jurd
1 parent 933b17b commit e61fd4a

File tree

6 files changed

+347
-14
lines changed

6 files changed

+347
-14
lines changed

doc/src/sgml/func.sgml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.485 2009/08/10 16:10:19 tgl Exp $ -->
1+
<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.486 2009/08/10 18:29:26 tgl Exp $ -->
22

33
<chapter id="functions">
44
<title>Functions and Operators</title>
@@ -5345,7 +5345,7 @@ SELECT SUBSTRING('XY1234Z', 'Y*?([0-9]{1,3})');
53455345
</row>
53465346
<row>
53475347
<entry><literal>EEEE</literal></entry>
5348-
<entry>scientific notation (not implemented)</entry>
5348+
<entry>exponent for scientific notation</entry>
53495349
</row>
53505350
</tbody>
53515351
</tgroup>
@@ -5404,6 +5404,15 @@ SELECT SUBSTRING('XY1234Z', 'Y*?([0-9]{1,3})');
54045404
(e.g., <literal>99.9V99</literal> is not allowed).
54055405
</para>
54065406
</listitem>
5407+
5408+
<listitem>
5409+
<para>
5410+
<literal>EEEE</literal> (scientific notation) cannot be used in
5411+
combination with any of the other special formatting patterns or
5412+
modifiers, and must be at the end of the format string
5413+
(e.g., <literal>9.99EEEE</literal> is a valid pattern).
5414+
</para>
5415+
</listitem>
54075416
</itemizedlist>
54085417
</para>
54095418

@@ -5605,6 +5614,10 @@ SELECT SUBSTRING('XY1234Z', 'Y*?([0-9]{1,3})');
56055614
<entry><literal>to_char(12.45, '99V9')</literal></entry>
56065615
<entry><literal>'&nbsp;125'</literal></entry>
56075616
</row>
5617+
<row>
5618+
<entry><literal>to_char(0.0004859, '9.99EEEE')</literal></entry>
5619+
<entry><literal>' 4.86e-04'</literal></entry>
5620+
</row>
56085621
</tbody>
56095622
</tgroup>
56105623
</table>

src/backend/utils/adt/formatting.c

Lines changed: 178 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* -----------------------------------------------------------------------
22
* formatting.c
33
*
4-
* $PostgreSQL: pgsql/src/backend/utils/adt/formatting.c,v 1.159 2009/07/06 19:11:39 heikki Exp $
4+
* $PostgreSQL: pgsql/src/backend/utils/adt/formatting.c,v 1.160 2009/08/10 18:29:26 tgl Exp $
55
*
66
*
77
* Portions Copyright (c) 1999-2009, PostgreSQL Global Development Group
@@ -335,6 +335,7 @@ typedef struct
335335
#define NUM_F_MULTI (1 << 11)
336336
#define NUM_F_PLUS_POST (1 << 12)
337337
#define NUM_F_MINUS_POST (1 << 13)
338+
#define NUM_F_EEEE (1 << 14)
338339

339340
#define NUM_LSIGN_PRE (-1)
340341
#define NUM_LSIGN_POST 1
@@ -355,6 +356,7 @@ typedef struct
355356
#define IS_PLUS(_f) ((_f)->flag & NUM_F_PLUS)
356357
#define IS_ROMAN(_f) ((_f)->flag & NUM_F_ROMAN)
357358
#define IS_MULTI(_f) ((_f)->flag & NUM_F_MULTI)
359+
#define IS_EEEE(_f) ((_f)->flag & NUM_F_EEEE)
358360

359361
/* ----------
360362
* Format picture cache
@@ -821,7 +823,7 @@ static const KeyWord NUM_keywords[] = {
821823
{"B", 1, NUM_B}, /* B */
822824
{"C", 1, NUM_C}, /* C */
823825
{"D", 1, NUM_D}, /* D */
824-
{"E", 1, NUM_E}, /* E */
826+
{"EEEE", 4, NUM_E}, /* E */
825827
{"FM", 2, NUM_FM}, /* F */
826828
{"G", 1, NUM_G}, /* G */
827829
{"L", 1, NUM_L}, /* L */
@@ -837,7 +839,7 @@ static const KeyWord NUM_keywords[] = {
837839
{"b", 1, NUM_B}, /* b */
838840
{"c", 1, NUM_C}, /* c */
839841
{"d", 1, NUM_D}, /* d */
840-
{"e", 1, NUM_E}, /* e */
842+
{"eeee", 4, NUM_E}, /* e */
841843
{"fm", 2, NUM_FM}, /* f */
842844
{"g", 1, NUM_G}, /* g */
843845
{"l", 1, NUM_L}, /* l */
@@ -1044,6 +1046,14 @@ NUMDesc_prepare(NUMDesc *num, FormatNode *n)
10441046
if (n->type != NODE_TYPE_ACTION)
10451047
return;
10461048

1049+
if (IS_EEEE(num) && n->key->id != NUM_E)
1050+
{
1051+
NUM_cache_remove(last_NUMCacheEntry);
1052+
ereport(ERROR,
1053+
(errcode(ERRCODE_SYNTAX_ERROR),
1054+
errmsg("\"EEEE\" must be the last pattern used")));
1055+
}
1056+
10471057
switch (n->key->id)
10481058
{
10491059
case NUM_9:
@@ -1217,10 +1227,25 @@ NUMDesc_prepare(NUMDesc *num, FormatNode *n)
12171227
break;
12181228

12191229
case NUM_E:
1220-
NUM_cache_remove(last_NUMCacheEntry);
1221-
ereport(ERROR,
1222-
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1223-
errmsg("\"E\" is not supported")));
1230+
if (IS_EEEE(num))
1231+
{
1232+
NUM_cache_remove(last_NUMCacheEntry);
1233+
ereport(ERROR,
1234+
(errcode(ERRCODE_SYNTAX_ERROR),
1235+
errmsg("cannot use \"EEEE\" twice")));
1236+
}
1237+
if (IS_BLANK(num) || IS_FILLMODE(num) || IS_LSIGN(num) ||
1238+
IS_BRACKET(num) || IS_MINUS(num) || IS_PLUS(num) ||
1239+
IS_ROMAN(num) || IS_MULTI(num))
1240+
{
1241+
NUM_cache_remove(last_NUMCacheEntry);
1242+
ereport(ERROR,
1243+
(errcode(ERRCODE_SYNTAX_ERROR),
1244+
errmsg("\"EEEE\" is incompatible with other formats"),
1245+
errdetail("\"EEEE\" may only be used together with digit and decimal point patterns.")));
1246+
}
1247+
num->flag |= NUM_F_EEEE;
1248+
break;
12241249
}
12251250

12261251
return;
@@ -4145,6 +4170,15 @@ NUM_processor(FormatNode *node, NUMDesc *Num, char *inout, char *number,
41454170
if (Np->Num->zero_start)
41464171
--Np->Num->zero_start;
41474172

4173+
if (IS_EEEE(Np->Num))
4174+
{
4175+
if (!Np->is_to_char)
4176+
ereport(ERROR,
4177+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
4178+
errmsg("\"EEEE\" not supported for input")));
4179+
return strcpy(inout, number);
4180+
}
4181+
41484182
/*
41494183
* Roman correction
41504184
*/
@@ -4153,7 +4187,7 @@ NUM_processor(FormatNode *node, NUMDesc *Num, char *inout, char *number,
41534187
if (!Np->is_to_char)
41544188
ereport(ERROR,
41554189
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
4156-
errmsg("\"RN\" not supported")));
4190+
errmsg("\"RN\" not supported for input")));
41574191

41584192
Np->Num->lsign = Np->Num->pre_lsign_num = Np->Num->post =
41594193
Np->Num->pre = Np->num_pre = Np->sign = 0;
@@ -4240,7 +4274,7 @@ NUM_processor(FormatNode *node, NUMDesc *Num, char *inout, char *number,
42404274

42414275
#ifdef DEBUG_TO_FROM_CHAR
42424276
elog(DEBUG_elog_output,
4243-
"\n\tSIGN: '%c'\n\tNUM: '%s'\n\tPRE: %d\n\tPOST: %d\n\tNUM_COUNT: %d\n\tNUM_PRE: %d\n\tSIGN_WROTE: %s\n\tZERO: %s\n\tZERO_START: %d\n\tZERO_END: %d\n\tLAST_RELEVANT: %s\n\tBRACKET: %s\n\tPLUS: %s\n\tMINUS: %s\n\tFILLMODE: %s\n\tROMAN: %s",
4277+
"\n\tSIGN: '%c'\n\tNUM: '%s'\n\tPRE: %d\n\tPOST: %d\n\tNUM_COUNT: %d\n\tNUM_PRE: %d\n\tSIGN_WROTE: %s\n\tZERO: %s\n\tZERO_START: %d\n\tZERO_END: %d\n\tLAST_RELEVANT: %s\n\tBRACKET: %s\n\tPLUS: %s\n\tMINUS: %s\n\tFILLMODE: %s\n\tROMAN: %s\n\tEEEE: %s",
42444278
Np->sign,
42454279
Np->number,
42464280
Np->Num->pre,
@@ -4256,7 +4290,8 @@ NUM_processor(FormatNode *node, NUMDesc *Num, char *inout, char *number,
42564290
IS_PLUS(Np->Num) ? "Yes" : "No",
42574291
IS_MINUS(Np->Num) ? "Yes" : "No",
42584292
IS_FILLMODE(Np->Num) ? "Yes" : "No",
4259-
IS_ROMAN(Np->Num) ? "Yes" : "No"
4293+
IS_ROMAN(Np->Num) ? "Yes" : "No",
4294+
IS_EEEE(Np->Num) ? "Yes" : "No"
42604295
);
42614296
#endif
42624297

@@ -4626,6 +4661,39 @@ numeric_to_char(PG_FUNCTION_ARGS)
46264661
int_to_roman(DatumGetInt32(DirectFunctionCall1(numeric_int4,
46274662
NumericGetDatum(x))));
46284663
}
4664+
else if (IS_EEEE(&Num))
4665+
{
4666+
orgnum = numeric_out_sci(value, Num.post);
4667+
4668+
/*
4669+
* numeric_out_sci() does not emit a sign for positive numbers. We
4670+
* need to add a space in this case so that positive and negative
4671+
* numbers are aligned. We also have to do the right thing for NaN.
4672+
*/
4673+
if (strcmp(orgnum, "NaN") == 0)
4674+
{
4675+
/*
4676+
* Allow 6 characters for the leading sign, the decimal point, "e",
4677+
* the exponent's sign and two exponent digits.
4678+
*/
4679+
numstr = (char *) palloc(Num.pre + Num.post + 7);
4680+
fill_str(numstr, '#', Num.pre + Num.post + 6);
4681+
*numstr = ' ';
4682+
*(numstr + Num.pre + 1) = '.';
4683+
}
4684+
else if (*orgnum != '-')
4685+
{
4686+
numstr = (char *) palloc(strlen(orgnum) + 2);
4687+
*numstr = ' ';
4688+
strcpy(numstr + 1, orgnum);
4689+
len = strlen(numstr);
4690+
}
4691+
else
4692+
{
4693+
numstr = orgnum;
4694+
len = strlen(orgnum);
4695+
}
4696+
}
46294697
else
46304698
{
46314699
Numeric val = value;
@@ -4707,6 +4775,23 @@ int4_to_char(PG_FUNCTION_ARGS)
47074775
*/
47084776
if (IS_ROMAN(&Num))
47094777
numstr = orgnum = int_to_roman(value);
4778+
else if (IS_EEEE(&Num))
4779+
{
4780+
/* we can do it easily because float8 won't lose any precision */
4781+
float8 val = (float8) value;
4782+
4783+
orgnum = (char *) palloc(MAXDOUBLEWIDTH + 1);
4784+
snprintf(orgnum, MAXDOUBLEWIDTH + 1, "%+.*e", Num.post, val);
4785+
4786+
/*
4787+
* Swap a leading positive sign for a space.
4788+
*/
4789+
if (*orgnum == '+')
4790+
*orgnum = ' ';
4791+
4792+
len = strlen(orgnum);
4793+
numstr = orgnum;
4794+
}
47104795
else
47114796
{
47124797
if (IS_MULTI(&Num))
@@ -4785,6 +4870,33 @@ int8_to_char(PG_FUNCTION_ARGS)
47854870
numstr = orgnum = int_to_roman(DatumGetInt32(
47864871
DirectFunctionCall1(int84, Int64GetDatum(value))));
47874872
}
4873+
else if (IS_EEEE(&Num))
4874+
{
4875+
/* to avoid loss of precision, must go via numeric not float8 */
4876+
Numeric val;
4877+
4878+
val = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
4879+
Int64GetDatum(value)));
4880+
orgnum = numeric_out_sci(val, Num.post);
4881+
4882+
/*
4883+
* numeric_out_sci() does not emit a sign for positive numbers. We
4884+
* need to add a space in this case so that positive and negative
4885+
* numbers are aligned. We don't have to worry about NaN here.
4886+
*/
4887+
if (*orgnum != '-')
4888+
{
4889+
numstr = (char *) palloc(strlen(orgnum) + 2);
4890+
*numstr = ' ';
4891+
strcpy(numstr + 1, orgnum);
4892+
len = strlen(numstr);
4893+
}
4894+
else
4895+
{
4896+
numstr = orgnum;
4897+
len = strlen(orgnum);
4898+
}
4899+
}
47884900
else
47894901
{
47904902
if (IS_MULTI(&Num))
@@ -4859,6 +4971,34 @@ float4_to_char(PG_FUNCTION_ARGS)
48594971

48604972
if (IS_ROMAN(&Num))
48614973
numstr = orgnum = int_to_roman((int) rint(value));
4974+
else if (IS_EEEE(&Num))
4975+
{
4976+
numstr = orgnum = (char *) palloc(MAXDOUBLEWIDTH + 1);
4977+
if (isnan(value) || is_infinite(value))
4978+
{
4979+
/*
4980+
* Allow 6 characters for the leading sign, the decimal point, "e",
4981+
* the exponent's sign and two exponent digits.
4982+
*/
4983+
numstr = (char *) palloc(Num.pre + Num.post + 7);
4984+
fill_str(numstr, '#', Num.pre + Num.post + 6);
4985+
*numstr = ' ';
4986+
*(numstr + Num.pre + 1) = '.';
4987+
}
4988+
else
4989+
{
4990+
snprintf(orgnum, MAXDOUBLEWIDTH + 1, "%+.*e", Num.post, value);
4991+
4992+
/*
4993+
* Swap a leading positive sign for a space.
4994+
*/
4995+
if (*orgnum == '+')
4996+
*orgnum = ' ';
4997+
4998+
len = strlen(orgnum);
4999+
numstr = orgnum;
5000+
}
5001+
}
48625002
else
48635003
{
48645004
float4 val = value;
@@ -4935,6 +5075,34 @@ float8_to_char(PG_FUNCTION_ARGS)
49355075

49365076
if (IS_ROMAN(&Num))
49375077
numstr = orgnum = int_to_roman((int) rint(value));
5078+
else if (IS_EEEE(&Num))
5079+
{
5080+
numstr = orgnum = (char *) palloc(MAXDOUBLEWIDTH + 1);
5081+
if (isnan(value) || is_infinite(value))
5082+
{
5083+
/*
5084+
* Allow 6 characters for the leading sign, the decimal point, "e",
5085+
* the exponent's sign and two exponent digits.
5086+
*/
5087+
numstr = (char *) palloc(Num.pre + Num.post + 7);
5088+
fill_str(numstr, '#', Num.pre + Num.post + 6);
5089+
*numstr = ' ';
5090+
*(numstr + Num.pre + 1) = '.';
5091+
}
5092+
else
5093+
{
5094+
snprintf(orgnum, MAXDOUBLEWIDTH + 1, "%+.*e", Num.post, value);
5095+
5096+
/*
5097+
* Swap a leading positive sign for a space.
5098+
*/
5099+
if (*orgnum == '+')
5100+
*orgnum = ' ';
5101+
5102+
len = strlen(orgnum);
5103+
numstr = orgnum;
5104+
}
5105+
}
49385106
else
49395107
{
49405108
float8 val = value;

0 commit comments

Comments
 (0)