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

Lines changed: 37 additions & 29 deletions
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

Lines changed: 32 additions & 0 deletions
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

Lines changed: 7 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy