Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4851940

Browse files
committed
Fix division-by-zero error in to_char() with 'EEEE' format.
This fixes a long-standing bug when using to_char() to format a numeric value in scientific notation -- if the value's exponent is less than -NUMERIC_MAX_DISPLAY_SCALE-1 (-1001), it produced a division-by-zero error. The reason for this error was that get_str_from_var_sci() divides its input by 10^exp, which it produced using power_var_int(). However, the underflow test in power_var_int() causes it to return zero if the result scale is too small. That's not a problem for power_var_int()'s only other caller, power_var(), since that limits the rscale to 1000, but in get_str_from_var_sci() the exponent can be much smaller, requiring a much larger rscale. Fix by introducing a new function to compute 10^exp directly, with no rscale limit. This also allows 10^exp to be computed more efficiently, without any numeric multiplication, division or rounding. Discussion: https://postgr.es/m/CAEZATCWhojfH4whaqgUKBe8D5jNHB8ytzemL-PnRx+KCTyMXmg@mail.gmail.com
1 parent 554a764 commit 4851940

File tree

3 files changed

+78
-29
lines changed

3 files changed

+78
-29
lines changed

src/backend/utils/adt/numeric.c

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -381,16 +381,6 @@ static const NumericDigit const_two_data[1] = {2};
381381
static const NumericVar const_two =
382382
{1, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_two_data};
383383

384-
#if DEC_DIGITS == 4 || DEC_DIGITS == 2
385-
static const NumericDigit const_ten_data[1] = {10};
386-
static const NumericVar const_ten =
387-
{1, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_ten_data};
388-
#elif DEC_DIGITS == 1
389-
static const NumericDigit const_ten_data[1] = {1};
390-
static const NumericVar const_ten =
391-
{1, 1, NUMERIC_POS, 0, NULL, (NumericDigit *) const_ten_data};
392-
#endif
393-
394384
#if DEC_DIGITS == 4
395385
static const NumericDigit const_zero_point_five_data[1] = {5000};
396386
#elif DEC_DIGITS == 2
@@ -529,6 +519,7 @@ static void power_var(const NumericVar *base, const NumericVar *exp,
529519
NumericVar *result);
530520
static void power_var_int(const NumericVar *base, int exp, NumericVar *result,
531521
int rscale);
522+
static void power_ten_int(int exp, NumericVar *result);
532523

533524
static int cmp_abs(const NumericVar *var1, const NumericVar *var2);
534525
static int cmp_abs_common(const NumericDigit *var1digits, int var1ndigits,
@@ -6015,9 +6006,7 @@ static char *
60156006
get_str_from_var_sci(const NumericVar *var, int rscale)
60166007
{
60176008
int32 exponent;
6018-
NumericVar denominator;
6019-
NumericVar significand;
6020-
int denom_scale;
6009+
NumericVar tmp_var;
60216010
size_t len;
60226011
char *str;
60236012
char *sig_out;
@@ -6054,25 +6043,16 @@ get_str_from_var_sci(const NumericVar *var, int rscale)
60546043
}
60556044

60566045
/*
6057-
* The denominator is set to 10 raised to the power of the exponent.
6058-
*
6059-
* We then divide var by the denominator to get the significand, rounding
6060-
* to rscale decimal digits in the process.
6046+
* Divide var by 10^exponent to get the significand, rounding to rscale
6047+
* decimal digits in the process.
60616048
*/
6062-
if (exponent < 0)
6063-
denom_scale = -exponent;
6064-
else
6065-
denom_scale = 0;
6066-
6067-
init_var(&denominator);
6068-
init_var(&significand);
6049+
init_var(&tmp_var);
60696050

6070-
power_var_int(&const_ten, exponent, &denominator, denom_scale);
6071-
div_var(var, &denominator, &significand, rscale, true);
6072-
sig_out = get_str_from_var(&significand);
6051+
power_ten_int(exponent, &tmp_var);
6052+
div_var(var, &tmp_var, &tmp_var, rscale, true);
6053+
sig_out = get_str_from_var(&tmp_var);
60736054

6074-
free_var(&denominator);
6075-
free_var(&significand);
6055+
free_var(&tmp_var);
60766056

60776057
/*
60786058
* Allocate space for the result.
@@ -8561,6 +8541,34 @@ power_var_int(const NumericVar *base, int exp, NumericVar *result, int rscale)
85618541
round_var(result, rscale);
85628542
}
85638543

8544+
/*
8545+
* power_ten_int() -
8546+
*
8547+
* Raise ten to the power of exp, where exp is an integer. Note that unlike
8548+
* power_var_int(), this does no overflow/underflow checking or rounding.
8549+
*/
8550+
static void
8551+
power_ten_int(int exp, NumericVar *result)
8552+
{
8553+
/* Construct the result directly, starting from 10^0 = 1 */
8554+
set_var_from_var(&const_one, result);
8555+
8556+
/* Scale needed to represent the result exactly */
8557+
result->dscale = exp < 0 ? -exp : 0;
8558+
8559+
/* Base-NBASE weight of result and remaining exponent */
8560+
if (exp >= 0)
8561+
result->weight = exp / DEC_DIGITS;
8562+
else
8563+
result->weight = (exp + 1) / DEC_DIGITS - 1;
8564+
8565+
exp -= result->weight * DEC_DIGITS;
8566+
8567+
/* Final adjustment of the result's single NBASE digit */
8568+
while (exp-- > 0)
8569+
result->digits[0] *= 10;
8570+
}
8571+
85648572

85658573
/* ----------------------------------------------------------------------
85668574
*

src/test/regress/expected/numeric.out

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1278,6 +1278,39 @@ SELECT '' AS to_char_36, to_char('100'::numeric, 'f"ool\\"999');
12781278
| fool\ 100
12791279
(1 row)
12801280

1281+
-- Test scientific notation with various exponents
1282+
WITH v(exp) AS
1283+
(VALUES(-16379),(-16378),(-1234),(-789),(-45),(-5),(-4),(-3),(-2),(-1),(0),
1284+
(1),(2),(3),(4),(5),(38),(275),(2345),(45678),(131070),(131071))
1285+
SELECT exp,
1286+
to_char(('1.2345e'||exp)::numeric, '9.999EEEE') as numeric
1287+
FROM v;
1288+
exp | numeric
1289+
--------+----------------
1290+
-16379 | 1.235e-16379
1291+
-16378 | 1.235e-16378
1292+
-1234 | 1.235e-1234
1293+
-789 | 1.235e-789
1294+
-45 | 1.235e-45
1295+
-5 | 1.235e-05
1296+
-4 | 1.235e-04
1297+
-3 | 1.235e-03
1298+
-2 | 1.235e-02
1299+
-1 | 1.235e-01
1300+
0 | 1.235e+00
1301+
1 | 1.235e+01
1302+
2 | 1.235e+02
1303+
3 | 1.235e+03
1304+
4 | 1.235e+04
1305+
5 | 1.235e+05
1306+
38 | 1.235e+38
1307+
275 | 1.235e+275
1308+
2345 | 1.235e+2345
1309+
45678 | 1.235e+45678
1310+
131070 | 1.235e+131070
1311+
131071 | 1.235e+131071
1312+
(22 rows)
1313+
12811314
-- TO_NUMBER()
12821315
--
12831316
SET lc_numeric = 'C';

src/test/regress/sql/numeric.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,14 @@ SELECT '' AS to_char_34, to_char('100'::numeric, 'f"\\ool"999');
798798
SELECT '' AS to_char_35, to_char('100'::numeric, 'f"ool\"999');
799799
SELECT '' AS to_char_36, to_char('100'::numeric, 'f"ool\\"999');
800800

801+
-- Test scientific notation with various exponents
802+
WITH v(exp) AS
803+
(VALUES(-16379),(-16378),(-1234),(-789),(-45),(-5),(-4),(-3),(-2),(-1),(0),
804+
(1),(2),(3),(4),(5),(38),(275),(2345),(45678),(131070),(131071))
805+
SELECT exp,
806+
to_char(('1.2345e'||exp)::numeric, '9.999EEEE') as numeric
807+
FROM v;
808+
801809
-- TO_NUMBER()
802810
--
803811
SET lc_numeric = 'C';

0 commit comments

Comments
 (0)