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)
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