Skip to content

Commit ecbdbdf

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 fa604e0 commit ecbdbdf

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
@@ -579,6 +569,7 @@ static void power_var(const NumericVar *base, const NumericVar *exp,
579569
NumericVar *result);
580570
static void power_var_int(const NumericVar *base, int exp, NumericVar *result,
581571
int rscale);
572+
static void power_ten_int(int exp, NumericVar *result);
582573

583574
static int cmp_abs(const NumericVar *var1, const NumericVar *var2);
584575
static int cmp_abs_common(const NumericDigit *var1digits, int var1ndigits,
@@ -7215,9 +7206,7 @@ static char *
72157206
get_str_from_var_sci(const NumericVar *var, int rscale)
72167207
{
72177208
int32 exponent;
7218-
NumericVar denominator;
7219-
NumericVar significand;
7220-
int denom_scale;
7209+
NumericVar tmp_var;
72217210
size_t len;
72227211
char *str;
72237212
char *sig_out;
@@ -7254,25 +7243,16 @@ get_str_from_var_sci(const NumericVar *var, int rscale)
72547243
}
72557244

72567245
/*
7257-
* The denominator is set to 10 raised to the power of the exponent.
7258-
*
7259-
* We then divide var by the denominator to get the significand, rounding
7260-
* to rscale decimal digits in the process.
7246+
* Divide var by 10^exponent to get the significand, rounding to rscale
7247+
* decimal digits in the process.
72617248
*/
7262-
if (exponent < 0)
7263-
denom_scale = -exponent;
7264-
else
7265-
denom_scale = 0;
7266-
7267-
init_var(&denominator);
7268-
init_var(&significand);
7249+
init_var(&tmp_var);
72697250

7270-
power_var_int(&const_ten, exponent, &denominator, denom_scale);
7271-
div_var(var, &denominator, &significand, rscale, true);
7272-
sig_out = get_str_from_var(&significand);
7251+
power_ten_int(exponent, &tmp_var);
7252+
div_var(var, &tmp_var, &tmp_var, rscale, true);
7253+
sig_out = get_str_from_var(&tmp_var);
72737254

7274-
free_var(&denominator);
7275-
free_var(&significand);
7255+
free_var(&tmp_var);
72767256

72777257
/*
72787258
* Allocate space for the result.
@@ -10480,6 +10460,34 @@ power_var_int(const NumericVar *base, int exp, NumericVar *result, int rscale)
1048010460
round_var(result, rscale);
1048110461
}
1048210462

10463+
/*
10464+
* power_ten_int() -
10465+
*
10466+
* Raise ten to the power of exp, where exp is an integer. Note that unlike
10467+
* power_var_int(), this does no overflow/underflow checking or rounding.
10468+
*/
10469+
static void
10470+
power_ten_int(int exp, NumericVar *result)
10471+
{
10472+
/* Construct the result directly, starting from 10^0 = 1 */
10473+
set_var_from_var(&const_one, result);
10474+
10475+
/* Scale needed to represent the result exactly */
10476+
result->dscale = exp < 0 ? -exp : 0;
10477+
10478+
/* Base-NBASE weight of result and remaining exponent */
10479+
if (exp >= 0)
10480+
result->weight = exp / DEC_DIGITS;
10481+
else
10482+
result->weight = (exp + 1) / DEC_DIGITS - 1;
10483+
10484+
exp -= result->weight * DEC_DIGITS;
10485+
10486+
/* Final adjustment of the result's single NBASE digit */
10487+
while (exp-- > 0)
10488+
result->digits[0] *= 10;
10489+
}
10490+
1048310491

1048410492
/* ----------------------------------------------------------------------
1048510493
*

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