aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/utils/adt/numeric.c66
-rw-r--r--src/test/regress/expected/numeric.out33
-rw-r--r--src/test/regress/sql/numeric.sql8
3 files changed, 78 insertions, 29 deletions
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 0d2c8333627..5b83e1dd627 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -383,16 +383,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_five_data[1] = {5000};
#elif DEC_DIGITS == 2
@@ -532,6 +522,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,
@@ -6159,9 +6150,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;
@@ -6198,25 +6187,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.
@@ -8729,6 +8709,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;
+}
+
/* ----------------------------------------------------------------------
*
diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out
index 579ebd79976..897096680eb 100644
--- a/src/test/regress/expected/numeric.out
+++ b/src/test/regress/expected/numeric.out
@@ -1278,6 +1278,39 @@ SELECT '' AS to_char_36, to_char('100'::numeric, 'f"ool\\"999');
| fool\ 100
(1 row)
+-- Test scientific notation with various exponents
+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)
+
-- TO_NUMBER()
--
SET lc_numeric = 'C';
diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sql
index 2ad4f3e7387..972e3aaec49 100644
--- a/src/test/regress/sql/numeric.sql
+++ b/src/test/regress/sql/numeric.sql
@@ -798,6 +798,14 @@ SELECT '' AS to_char_34, to_char('100'::numeric, 'f"\\ool"999');
SELECT '' AS to_char_35, to_char('100'::numeric, 'f"ool\"999');
SELECT '' AS to_char_36, to_char('100'::numeric, 'f"ool\\"999');
+-- Test scientific notation with various exponents
+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;
+
-- TO_NUMBER()
--
SET lc_numeric = 'C';