Skip to content

Commit f1ee153

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 45d256c commit f1ee153

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];
@@ -196,98 +197,87 @@ static void IsPagerNeeded(const printTableContent *cont, const int extra_lines,
196197
static void print_aligned_vertical(const printTableContent *cont, FILE *fout);
197198

198199

200+
/* Count number of digits in integral part of number */
199201
static int
200202
integer_digits(const char *my_str)
201203
{
202-
int frac_len;
203-
204-
if (my_str[0] == '-')
204+
/* ignoring any sign ... */
205+
if (my_str[0] == '-' || my_str[0] == '+')
205206
my_str++;
206-
207-
frac_len = strchr(my_str, '.') ? strlen(strchr(my_str, '.')) : 0;
208-
209-
return strlen(my_str) - frac_len;
207+
/* ... count initial integral digits */
208+
return strspn(my_str, "0123456789");
210209
}
211210

212-
/* Return additional length required for locale-aware numeric output */
211+
/* Compute additional length required for locale-aware numeric output */
213212
static int
214213
additional_numeric_locale_len(const char *my_str)
215214
{
216215
int int_len = integer_digits(my_str),
217216
len = 0;
218-
int groupdigits = atoi(grouping);
219217

220-
if (int_len > 0)
221-
/* Don't count a leading separator */
222-
len = (int_len / groupdigits - (int_len % groupdigits == 0)) *
223-
strlen(thousands_sep);
218+
/* Account for added thousands_sep instances */
219+
if (int_len > groupdigits)
220+
len += ((int_len - 1) / groupdigits) * strlen(thousands_sep);
224221

222+
/* Account for possible additional length of decimal_point */
225223
if (strchr(my_str, '.') != NULL)
226-
len += strlen(decimal_point) - strlen(".");
224+
len += strlen(decimal_point) - 1;
227225

228226
return len;
229227
}
230228

231-
static int
232-
strlen_with_numeric_locale(const char *my_str)
233-
{
234-
return strlen(my_str) + additional_numeric_locale_len(my_str);
235-
}
236-
237229
/*
238230
* Returns the appropriately formatted string in a new allocated block,
239231
* caller must free
240232
*/
241233
static char *
242234
format_numeric_locale(const char *my_str)
243235
{
236+
int new_len = strlen(my_str) + additional_numeric_locale_len(my_str);
237+
char *new_str = pg_malloc(new_len + 1);
238+
int int_len = integer_digits(my_str);
244239
int i,
245-
j,
246-
int_len = integer_digits(my_str),
247240
leading_digits;
248-
int groupdigits = atoi(grouping);
249-
int new_str_start = 0;
250-
char *new_str = pg_malloc(strlen_with_numeric_locale(my_str) + 1);
241+
int new_str_pos = 0;
251242

252-
leading_digits = (int_len % groupdigits != 0) ?
253-
int_len % groupdigits : groupdigits;
243+
/* number of digits in first thousands group */
244+
leading_digits = int_len % groupdigits;
245+
if (leading_digits == 0)
246+
leading_digits = groupdigits;
254247

255-
if (my_str[0] == '-') /* skip over sign, affects grouping
256-
* calculations */
248+
/* process sign */
249+
if (my_str[0] == '-' || my_str[0] == '+')
257250
{
258-
new_str[0] = my_str[0];
251+
new_str[new_str_pos++] = my_str[0];
259252
my_str++;
260-
new_str_start = 1;
261253
}
262254

263-
for (i = 0, j = new_str_start;; i++, j++)
255+
/* process integer part of number */
256+
for (i = 0; i < int_len; i++)
264257
{
265-
/* Hit decimal point? */
266-
if (my_str[i] == '.')
258+
/* Time to insert separator? */
259+
if (i > 0 && --leading_digits == 0)
267260
{
268-
strcpy(&new_str[j], decimal_point);
269-
j += strlen(decimal_point);
270-
/* add fractional part */
271-
strcpy(&new_str[j], &my_str[i] + 1);
272-
break;
261+
strcpy(&new_str[new_str_pos], thousands_sep);
262+
new_str_pos += strlen(thousands_sep);
263+
leading_digits = groupdigits;
273264
}
265+
new_str[new_str_pos++] = my_str[i];
266+
}
274267

275-
/* End of string? */
276-
if (my_str[i] == '\0')
277-
{
278-
new_str[j] = '\0';
279-
break;
280-
}
268+
/* handle decimal point if any */
269+
if (my_str[i] == '.')
270+
{
271+
strcpy(&new_str[new_str_pos], decimal_point);
272+
new_str_pos += strlen(decimal_point);
273+
i++;
274+
}
281275

282-
/* Add separator? */
283-
if (i != 0 && (i - leading_digits) % groupdigits == 0)
284-
{
285-
strcpy(&new_str[j], thousands_sep);
286-
j += strlen(thousands_sep);
287-
}
276+
/* copy the rest (fractional digits and/or exponent, and \0 terminator) */
277+
strcpy(&new_str[new_str_pos], &my_str[i]);
288278

289-
new_str[j] = my_str[i];
290-
}
279+
/* assert we didn't underestimate new_len (an overestimate is OK) */
280+
Assert(strlen(new_str) <= new_len);
291281

292282
return new_str;
293283
}
@@ -3241,10 +3231,11 @@ setDecimalLocale(void)
32413231
decimal_point = pg_strdup(extlconv->decimal_point);
32423232
else
32433233
decimal_point = "."; /* SQL output standard */
3234+
32443235
if (*extlconv->grouping && atoi(extlconv->grouping) > 0)
3245-
grouping = pg_strdup(extlconv->grouping);
3236+
groupdigits = atoi(extlconv->grouping);
32463237
else
3247-
grouping = "3"; /* most common */
3238+
groupdigits = 3; /* most common */
32483239

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

0 commit comments

Comments
 (0)