Skip to content

Commit 348dd28

Browse files
committed
Fix psql's code for locale-aware formatting of numeric output.
This code did the wrong thing entirely for numbers with an exponent but no decimal point (e.g., '1e6'), as reported by Jeff Janes in bug #13636. More generally, it made lots of unverified assumptions about what the input string could possibly look like. Rearrange so that it only fools with leading digits that it's directly verified are there, and an immediately adjacent decimal point. While at it, get rid of some useless inefficiencies, like converting the grouping count string to integer over and over (and over). This has been broken for a long time, so back-patch to all supported branches.
1 parent 0da864c commit 348dd28

File tree

1 file changed

+47
-56
lines changed

1 file changed

+47
-56
lines changed

src/bin/psql/print.c

+47-56
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@
3939
*/
4040
volatile bool cancel_pressed = false;
4141

42+
/* info for locale-aware numeric formatting; set up by setDecimalLocale() */
4243
static char *decimal_point;
43-
static char *grouping;
44+
static int groupdigits;
4445
static char *thousands_sep;
4546

4647
static char default_footer[100];
@@ -130,98 +131,87 @@ static void IsPagerNeeded(const printTableContent *cont, const int extra_lines,
130131
static void print_aligned_vertical(const printTableContent *cont, FILE *fout);
131132

132133

134+
/* Count number of digits in integral part of number */
133135
static int
134136
integer_digits(const char *my_str)
135137
{
136-
int frac_len;
137-
138-
if (my_str[0] == '-')
138+
/* ignoring any sign ... */
139+
if (my_str[0] == '-' || my_str[0] == '+')
139140
my_str++;
140-
141-
frac_len = strchr(my_str, '.') ? strlen(strchr(my_str, '.')) : 0;
142-
143-
return strlen(my_str) - frac_len;
141+
/* ... count initial integral digits */
142+
return strspn(my_str, "0123456789");
144143
}
145144

146-
/* Return additional length required for locale-aware numeric output */
145+
/* Compute additional length required for locale-aware numeric output */
147146
static int
148147
additional_numeric_locale_len(const char *my_str)
149148
{
150149
int int_len = integer_digits(my_str),
151150
len = 0;
152-
int groupdigits = atoi(grouping);
153151

154-
if (int_len > 0)
155-
/* Don't count a leading separator */
156-
len = (int_len / groupdigits - (int_len % groupdigits == 0)) *
157-
strlen(thousands_sep);
152+
/* Account for added thousands_sep instances */
153+
if (int_len > groupdigits)
154+
len += ((int_len - 1) / groupdigits) * strlen(thousands_sep);
158155

156+
/* Account for possible additional length of decimal_point */
159157
if (strchr(my_str, '.') != NULL)
160-
len += strlen(decimal_point) - strlen(".");
158+
len += strlen(decimal_point) - 1;
161159

162160
return len;
163161
}
164162

165-
static int
166-
strlen_with_numeric_locale(const char *my_str)
167-
{
168-
return strlen(my_str) + additional_numeric_locale_len(my_str);
169-
}
170-
171163
/*
172164
* Returns the appropriately formatted string in a new allocated block,
173165
* caller must free
174166
*/
175167
static char *
176168
format_numeric_locale(const char *my_str)
177169
{
170+
int new_len = strlen(my_str) + additional_numeric_locale_len(my_str);
171+
char *new_str = pg_malloc(new_len + 1);
172+
int int_len = integer_digits(my_str);
178173
int i,
179-
j,
180-
int_len = integer_digits(my_str),
181174
leading_digits;
182-
int groupdigits = atoi(grouping);
183-
int new_str_start = 0;
184-
char *new_str = pg_malloc(strlen_with_numeric_locale(my_str) + 1);
175+
int new_str_pos = 0;
185176

186-
leading_digits = (int_len % groupdigits != 0) ?
187-
int_len % groupdigits : groupdigits;
177+
/* number of digits in first thousands group */
178+
leading_digits = int_len % groupdigits;
179+
if (leading_digits == 0)
180+
leading_digits = groupdigits;
188181

189-
if (my_str[0] == '-') /* skip over sign, affects grouping
190-
* calculations */
182+
/* process sign */
183+
if (my_str[0] == '-' || my_str[0] == '+')
191184
{
192-
new_str[0] = my_str[0];
185+
new_str[new_str_pos++] = my_str[0];
193186
my_str++;
194-
new_str_start = 1;
195187
}
196188

197-
for (i = 0, j = new_str_start;; i++, j++)
189+
/* process integer part of number */
190+
for (i = 0; i < int_len; i++)
198191
{
199-
/* Hit decimal point? */
200-
if (my_str[i] == '.')
192+
/* Time to insert separator? */
193+
if (i > 0 && --leading_digits == 0)
201194
{
202-
strcpy(&new_str[j], decimal_point);
203-
j += strlen(decimal_point);
204-
/* add fractional part */
205-
strcpy(&new_str[j], &my_str[i] + 1);
206-
break;
195+
strcpy(&new_str[new_str_pos], thousands_sep);
196+
new_str_pos += strlen(thousands_sep);
197+
leading_digits = groupdigits;
207198
}
199+
new_str[new_str_pos++] = my_str[i];
200+
}
208201

209-
/* End of string? */
210-
if (my_str[i] == '\0')
211-
{
212-
new_str[j] = '\0';
213-
break;
214-
}
202+
/* handle decimal point if any */
203+
if (my_str[i] == '.')
204+
{
205+
strcpy(&new_str[new_str_pos], decimal_point);
206+
new_str_pos += strlen(decimal_point);
207+
i++;
208+
}
215209

216-
/* Add separator? */
217-
if (i != 0 && (i - leading_digits) % groupdigits == 0)
218-
{
219-
strcpy(&new_str[j], thousands_sep);
220-
j += strlen(thousands_sep);
221-
}
210+
/* copy the rest (fractional digits and/or exponent, and \0 terminator) */
211+
strcpy(&new_str[new_str_pos], &my_str[i]);
222212

223-
new_str[j] = my_str[i];
224-
}
213+
/* assert we didn't underestimate new_len (an overestimate is OK) */
214+
Assert(strlen(new_str) <= new_len);
225215

226216
return new_str;
227217
}
@@ -2678,10 +2668,11 @@ setDecimalLocale(void)
26782668
decimal_point = pg_strdup(extlconv->decimal_point);
26792669
else
26802670
decimal_point = "."; /* SQL output standard */
2671+
26812672
if (*extlconv->grouping && atoi(extlconv->grouping) > 0)
2682-
grouping = pg_strdup(extlconv->grouping);
2673+
groupdigits = atoi(extlconv->grouping);
26832674
else
2684-
grouping = "3"; /* most common */
2675+
groupdigits = 3; /* most common */
26852676

26862677
/* similar code exists in formatting.c */
26872678
if (*extlconv->thousands_sep)

0 commit comments

Comments
 (0)