diff options
author | Dean Rasheed <dean.a.rasheed@gmail.com> | 2021-08-05 09:24:11 +0100 |
---|---|---|
committer | Dean Rasheed <dean.a.rasheed@gmail.com> | 2021-08-05 09:24:11 +0100 |
commit | 226ec49ffd78c0f246da8fceb3094991dd2302ff (patch) | |
tree | 3b957d3156fdd558352ad87de55104c328cb619f /src/backend/utils/adt/numeric.c | |
parent | 87bff68840d542011ed8f60427502fb90fdf2873 (diff) | |
download | postgresql-226ec49ffd78c0f246da8fceb3094991dd2302ff.tar.gz postgresql-226ec49ffd78c0f246da8fceb3094991dd2302ff.zip |
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
Diffstat (limited to 'src/backend/utils/adt/numeric.c')
-rw-r--r-- | src/backend/utils/adt/numeric.c | 66 |
1 files changed, 37 insertions, 29 deletions
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index 4ec5cbe3dc5..13bb9682a1b 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -428,16 +428,6 @@ static const NumericDigit const_two_data[1] = {2}; static const NumericVar const_two = {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 static const NumericDigit const_zero_point_nine_data[1] = {9000}; #elif DEC_DIGITS == 2 @@ -582,6 +572,7 @@ static void power_var(const NumericVar *base, const NumericVar *exp, NumericVar *result); static void power_var_int(const NumericVar *base, int exp, NumericVar *result, 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_common(const NumericDigit *var1digits, int var1ndigits, @@ -7210,9 +7201,7 @@ static char * get_str_from_var_sci(const NumericVar *var, int rscale) { int32 exponent; - NumericVar denominator; - NumericVar significand; - int denom_scale; + NumericVar tmp_var; size_t len; char *str; char *sig_out; @@ -7249,25 +7238,16 @@ get_str_from_var_sci(const NumericVar *var, int rscale) } /* - * The denominator is set to 10 raised to the power of the exponent. - * - * We then divide var by the denominator to get the significand, rounding - * to rscale decimal digits in the process. + * Divide var by 10^exponent to get the significand, rounding to rscale + * decimal digits in the process. */ - if (exponent < 0) - denom_scale = -exponent; - else - denom_scale = 0; - - init_var(&denominator); - init_var(&significand); + init_var(&tmp_var); - power_var_int(&const_ten, exponent, &denominator, denom_scale); - div_var(var, &denominator, &significand, rscale, true); - sig_out = get_str_from_var(&significand); + power_ten_int(exponent, &tmp_var); + div_var(var, &tmp_var, &tmp_var, rscale, true); + sig_out = get_str_from_var(&tmp_var); - free_var(&denominator); - free_var(&significand); + free_var(&tmp_var); /* * Allocate space for the result. @@ -10519,6 +10499,34 @@ power_var_int(const NumericVar *base, int exp, NumericVar *result, int 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; +} + /* ---------------------------------------------------------------------- * |