Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 226ec49

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 87bff68 commit 226ec49

File tree

3 files changed

+76
-29
lines changed

3 files changed

+76
-29
lines changed

src/backend/utils/adt/numeric.c

+37-29
Original file line numberDiff line numberDiff line change
@@ -428,16 +428,6 @@ static const NumericDigit const_two_data[1] = {2};
428428
static const NumericVar const_two =
429429
{1, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_two_data};
430430

431-
#if DEC_DIGITS == 4 || DEC_DIGITS == 2
432-
static const NumericDigit const_ten_data[1] = {10};
433-
static const NumericVar const_ten =
434-
{1, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_ten_data};
435-
#elif DEC_DIGITS == 1
436-
static const NumericDigit const_ten_data[1] = {1};
437-
static const NumericVar const_ten =
438-
{1, 1, NUMERIC_POS, 0, NULL, (NumericDigit *) const_ten_data};
439-
#endif
440-
441431
#if DEC_DIGITS == 4
442432
static const NumericDigit const_zero_point_nine_data[1] = {9000};
443433
#elif DEC_DIGITS == 2
@@ -582,6 +572,7 @@ static void power_var(const NumericVar *base, const NumericVar *exp,
582572
NumericVar *result);
583573
static void power_var_int(const NumericVar *base, int exp, NumericVar *result,
584574
int rscale);
575+
static void power_ten_int(int exp, NumericVar *result);
585576

586577
static int cmp_abs(const NumericVar *var1, const NumericVar *var2);
587578
static int cmp_abs_common(const NumericDigit *var1digits, int var1ndigits,
@@ -7210,9 +7201,7 @@ static char *
72107201
get_str_from_var_sci(const NumericVar *var, int rscale)
72117202
{
72127203
int32 exponent;
7213-
NumericVar denominator;
7214-
NumericVar significand;
7215-
int denom_scale;
7204+
NumericVar tmp_var;
72167205
size_t len;
72177206
char *str;
72187207
char *sig_out;
@@ -7249,25 +7238,16 @@ get_str_from_var_sci(const NumericVar *var, int rscale)
72497238
}
72507239

72517240
/*
7252-
* The denominator is set to 10 raised to the power of the exponent.
7253-
*
7254-
* We then divide var by the denominator to get the significand, rounding
7255-
* to rscale decimal digits in the process.
7241+
* Divide var by 10^exponent to get the significand, rounding to rscale
7242+
* decimal digits in the process.
72567243
*/
7257-
if (exponent < 0)
7258-
denom_scale = -exponent;
7259-
else
7260-
denom_scale = 0;
7261-
7262-
init_var(&denominator);
7263-
init_var(&significand);
7244+
init_var(&tmp_var);
72647245

7265-
power_var_int(&const_ten, exponent, &denominator, denom_scale);
7266-
div_var(var, &denominator, &significand, rscale, true);
7267-
sig_out = get_str_from_var(&significand);
7246+
power_ten_int(exponent, &tmp_var);
7247+
div_var(var, &tmp_var, &tmp_var, rscale, true);
7248+
sig_out = get_str_from_var(&tmp_var);
72687249

7269-
free_var(&denominator);
7270-
free_var(&significand);
7250+
free_var(&tmp_var);
72717251

72727252
/*
72737253
* Allocate space for the result.
@@ -10519,6 +10499,34 @@ power_var_int(const NumericVar *base, int exp, NumericVar *result, int rscale)
1051910499
round_var(result, rscale);
1052010500
}
1052110501

10502+
/*
10503+
* power_ten_int() -
10504+
*
10505+
* Raise ten to the power of exp, where exp is an integer. Note that unlike
10506+
* power_var_int(), this does no overflow/underflow checking or rounding.
10507+
*/
10508+
static void
10509+
power_ten_int(int exp, NumericVar *result)
10510+
{
10511+
/* Construct the result directly, starting from 10^0 = 1 */
10512+
set_var_from_var(&const_one, result);
10513+
10514+
/* Scale needed to represent the result exactly */
10515+
result->dscale = exp < 0 ? -exp : 0;
10516+
10517+
/* Base-NBASE weight of result and remaining exponent */
10518+
if (exp >= 0)
10519+
result->weight = exp / DEC_DIGITS;
10520+
else
10521+
result->weight = (exp + 1) / DEC_DIGITS - 1;
10522+
10523+
exp -= result->weight * DEC_DIGITS;
10524+
10525+
/* Final adjustment of the result's single NBASE digit */
10526+
while (exp-- > 0)
10527+
result->digits[0] *= 10;
10528+
}
10529+
1052210530

1052310531
/* ----------------------------------------------------------------------
1052410532
*

src/test/regress/expected/numeric.out

+32
Original file line numberDiff line numberDiff line change
@@ -1794,6 +1794,38 @@ FROM v;
17941794
NaN | #.####### | #.####### | #.#######
17951795
(7 rows)
17961796

1797+
WITH v(exp) AS
1798+
(VALUES(-16379),(-16378),(-1234),(-789),(-45),(-5),(-4),(-3),(-2),(-1),(0),
1799+
(1),(2),(3),(4),(5),(38),(275),(2345),(45678),(131070),(131071))
1800+
SELECT exp,
1801+
to_char(('1.2345e'||exp)::numeric, '9.999EEEE') as numeric
1802+
FROM v;
1803+
exp | numeric
1804+
--------+----------------
1805+
-16379 | 1.235e-16379
1806+
-16378 | 1.235e-16378
1807+
-1234 | 1.235e-1234
1808+
-789 | 1.235e-789
1809+
-45 | 1.235e-45
1810+
-5 | 1.235e-05
1811+
-4 | 1.235e-04
1812+
-3 | 1.235e-03
1813+
-2 | 1.235e-02
1814+
-1 | 1.235e-01
1815+
0 | 1.235e+00
1816+
1 | 1.235e+01
1817+
2 | 1.235e+02
1818+
3 | 1.235e+03
1819+
4 | 1.235e+04
1820+
5 | 1.235e+05
1821+
38 | 1.235e+38
1822+
275 | 1.235e+275
1823+
2345 | 1.235e+2345
1824+
45678 | 1.235e+45678
1825+
131070 | 1.235e+131070
1826+
131071 | 1.235e+131071
1827+
(22 rows)
1828+
17971829
WITH v(val) AS
17981830
(VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan'))
17991831
SELECT val,

src/test/regress/sql/numeric.sql

+7
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,13 @@ SELECT val,
939939
to_char(val::float4, '9.999EEEE') as float4
940940
FROM v;
941941

942+
WITH v(exp) AS
943+
(VALUES(-16379),(-16378),(-1234),(-789),(-45),(-5),(-4),(-3),(-2),(-1),(0),
944+
(1),(2),(3),(4),(5),(38),(275),(2345),(45678),(131070),(131071))
945+
SELECT exp,
946+
to_char(('1.2345e'||exp)::numeric, '9.999EEEE') as numeric
947+
FROM v;
948+
942949
WITH v(val) AS
943950
(VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan'))
944951
SELECT val,

0 commit comments

Comments
 (0)