mirror of
https://github.com/postgres/postgres.git
synced 2025-05-05 09:19:17 +03:00
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
This commit is contained in:
parent
fa604e0dd0
commit
ecbdbdfd90
@ -428,16 +428,6 @@ static const NumericDigit const_two_data[1] = {2};
|
|||||||
static const NumericVar const_two =
|
static const NumericVar const_two =
|
||||||
{1, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_two_data};
|
{1, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_two_data};
|
||||||
|
|
||||||
#if DEC_DIGITS == 4 || DEC_DIGITS == 2
|
|
||||||
static const NumericDigit const_ten_data[1] = {10};
|
|
||||||
static const NumericVar const_ten =
|
|
||||||
{1, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_ten_data};
|
|
||||||
#elif DEC_DIGITS == 1
|
|
||||||
static const NumericDigit const_ten_data[1] = {1};
|
|
||||||
static const NumericVar const_ten =
|
|
||||||
{1, 1, NUMERIC_POS, 0, NULL, (NumericDigit *) const_ten_data};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if DEC_DIGITS == 4
|
#if DEC_DIGITS == 4
|
||||||
static const NumericDigit const_zero_point_nine_data[1] = {9000};
|
static const NumericDigit const_zero_point_nine_data[1] = {9000};
|
||||||
#elif DEC_DIGITS == 2
|
#elif DEC_DIGITS == 2
|
||||||
@ -579,6 +569,7 @@ static void power_var(const NumericVar *base, const NumericVar *exp,
|
|||||||
NumericVar *result);
|
NumericVar *result);
|
||||||
static void power_var_int(const NumericVar *base, int exp, NumericVar *result,
|
static void power_var_int(const NumericVar *base, int exp, NumericVar *result,
|
||||||
int rscale);
|
int rscale);
|
||||||
|
static void power_ten_int(int exp, NumericVar *result);
|
||||||
|
|
||||||
static int cmp_abs(const NumericVar *var1, const NumericVar *var2);
|
static int cmp_abs(const NumericVar *var1, const NumericVar *var2);
|
||||||
static int cmp_abs_common(const NumericDigit *var1digits, int var1ndigits,
|
static int cmp_abs_common(const NumericDigit *var1digits, int var1ndigits,
|
||||||
@ -7215,9 +7206,7 @@ static char *
|
|||||||
get_str_from_var_sci(const NumericVar *var, int rscale)
|
get_str_from_var_sci(const NumericVar *var, int rscale)
|
||||||
{
|
{
|
||||||
int32 exponent;
|
int32 exponent;
|
||||||
NumericVar denominator;
|
NumericVar tmp_var;
|
||||||
NumericVar significand;
|
|
||||||
int denom_scale;
|
|
||||||
size_t len;
|
size_t len;
|
||||||
char *str;
|
char *str;
|
||||||
char *sig_out;
|
char *sig_out;
|
||||||
@ -7254,25 +7243,16 @@ get_str_from_var_sci(const NumericVar *var, int rscale)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The denominator is set to 10 raised to the power of the exponent.
|
* Divide var by 10^exponent to get the significand, rounding to rscale
|
||||||
*
|
* decimal digits in the process.
|
||||||
* We then divide var by the denominator to get the significand, rounding
|
|
||||||
* to rscale decimal digits in the process.
|
|
||||||
*/
|
*/
|
||||||
if (exponent < 0)
|
init_var(&tmp_var);
|
||||||
denom_scale = -exponent;
|
|
||||||
else
|
|
||||||
denom_scale = 0;
|
|
||||||
|
|
||||||
init_var(&denominator);
|
power_ten_int(exponent, &tmp_var);
|
||||||
init_var(&significand);
|
div_var(var, &tmp_var, &tmp_var, rscale, true);
|
||||||
|
sig_out = get_str_from_var(&tmp_var);
|
||||||
|
|
||||||
power_var_int(&const_ten, exponent, &denominator, denom_scale);
|
free_var(&tmp_var);
|
||||||
div_var(var, &denominator, &significand, rscale, true);
|
|
||||||
sig_out = get_str_from_var(&significand);
|
|
||||||
|
|
||||||
free_var(&denominator);
|
|
||||||
free_var(&significand);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Allocate space for the result.
|
* Allocate space for the result.
|
||||||
@ -10480,6 +10460,34 @@ power_var_int(const NumericVar *base, int exp, NumericVar *result, int rscale)
|
|||||||
round_var(result, rscale);
|
round_var(result, rscale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* power_ten_int() -
|
||||||
|
*
|
||||||
|
* Raise ten to the power of exp, where exp is an integer. Note that unlike
|
||||||
|
* power_var_int(), this does no overflow/underflow checking or rounding.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
power_ten_int(int exp, NumericVar *result)
|
||||||
|
{
|
||||||
|
/* Construct the result directly, starting from 10^0 = 1 */
|
||||||
|
set_var_from_var(&const_one, result);
|
||||||
|
|
||||||
|
/* Scale needed to represent the result exactly */
|
||||||
|
result->dscale = exp < 0 ? -exp : 0;
|
||||||
|
|
||||||
|
/* Base-NBASE weight of result and remaining exponent */
|
||||||
|
if (exp >= 0)
|
||||||
|
result->weight = exp / DEC_DIGITS;
|
||||||
|
else
|
||||||
|
result->weight = (exp + 1) / DEC_DIGITS - 1;
|
||||||
|
|
||||||
|
exp -= result->weight * DEC_DIGITS;
|
||||||
|
|
||||||
|
/* Final adjustment of the result's single NBASE digit */
|
||||||
|
while (exp-- > 0)
|
||||||
|
result->digits[0] *= 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------
|
/* ----------------------------------------------------------------------
|
||||||
*
|
*
|
||||||
|
@ -1794,6 +1794,38 @@ FROM v;
|
|||||||
NaN | #.####### | #.####### | #.#######
|
NaN | #.####### | #.####### | #.#######
|
||||||
(7 rows)
|
(7 rows)
|
||||||
|
|
||||||
|
WITH v(exp) AS
|
||||||
|
(VALUES(-16379),(-16378),(-1234),(-789),(-45),(-5),(-4),(-3),(-2),(-1),(0),
|
||||||
|
(1),(2),(3),(4),(5),(38),(275),(2345),(45678),(131070),(131071))
|
||||||
|
SELECT exp,
|
||||||
|
to_char(('1.2345e'||exp)::numeric, '9.999EEEE') as numeric
|
||||||
|
FROM v;
|
||||||
|
exp | numeric
|
||||||
|
--------+----------------
|
||||||
|
-16379 | 1.235e-16379
|
||||||
|
-16378 | 1.235e-16378
|
||||||
|
-1234 | 1.235e-1234
|
||||||
|
-789 | 1.235e-789
|
||||||
|
-45 | 1.235e-45
|
||||||
|
-5 | 1.235e-05
|
||||||
|
-4 | 1.235e-04
|
||||||
|
-3 | 1.235e-03
|
||||||
|
-2 | 1.235e-02
|
||||||
|
-1 | 1.235e-01
|
||||||
|
0 | 1.235e+00
|
||||||
|
1 | 1.235e+01
|
||||||
|
2 | 1.235e+02
|
||||||
|
3 | 1.235e+03
|
||||||
|
4 | 1.235e+04
|
||||||
|
5 | 1.235e+05
|
||||||
|
38 | 1.235e+38
|
||||||
|
275 | 1.235e+275
|
||||||
|
2345 | 1.235e+2345
|
||||||
|
45678 | 1.235e+45678
|
||||||
|
131070 | 1.235e+131070
|
||||||
|
131071 | 1.235e+131071
|
||||||
|
(22 rows)
|
||||||
|
|
||||||
WITH v(val) AS
|
WITH v(val) AS
|
||||||
(VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan'))
|
(VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan'))
|
||||||
SELECT val,
|
SELECT val,
|
||||||
|
@ -939,6 +939,13 @@ SELECT val,
|
|||||||
to_char(val::float4, '9.999EEEE') as float4
|
to_char(val::float4, '9.999EEEE') as float4
|
||||||
FROM v;
|
FROM v;
|
||||||
|
|
||||||
|
WITH v(exp) AS
|
||||||
|
(VALUES(-16379),(-16378),(-1234),(-789),(-45),(-5),(-4),(-3),(-2),(-1),(0),
|
||||||
|
(1),(2),(3),(4),(5),(38),(275),(2345),(45678),(131070),(131071))
|
||||||
|
SELECT exp,
|
||||||
|
to_char(('1.2345e'||exp)::numeric, '9.999EEEE') as numeric
|
||||||
|
FROM v;
|
||||||
|
|
||||||
WITH v(val) AS
|
WITH v(val) AS
|
||||||
(VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan'))
|
(VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan'))
|
||||||
SELECT val,
|
SELECT val,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user