aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/numeric.c
diff options
context:
space:
mode:
authorDean Rasheed <dean.a.rasheed@gmail.com>2021-08-05 09:24:11 +0100
committerDean Rasheed <dean.a.rasheed@gmail.com>2021-08-05 09:24:11 +0100
commit226ec49ffd78c0f246da8fceb3094991dd2302ff (patch)
tree3b957d3156fdd558352ad87de55104c328cb619f /src/backend/utils/adt/numeric.c
parent87bff68840d542011ed8f60427502fb90fdf2873 (diff)
downloadpostgresql-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.c66
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;
+}
+
/* ----------------------------------------------------------------------
*