diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index 87ffdf3fed..9677105ebe 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 @@ -579,6 +569,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, @@ -7215,9 +7206,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; @@ -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. - * - * 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(&tmp_var); - init_var(&denominator); - init_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); - power_var_int(&const_ten, exponent, &denominator, denom_scale); - div_var(var, &denominator, &significand, rscale, true); - sig_out = get_str_from_var(&significand); - - free_var(&denominator); - free_var(&significand); + free_var(&tmp_var); /* * 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); } +/* + * 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 9d7ff125b0..2e3e76bd64 100644 --- a/src/test/regress/expected/numeric.out +++ b/src/test/regress/expected/numeric.out @@ -1794,6 +1794,38 @@ FROM v; NaN | #.####### | #.####### | #.####### (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 (VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan')) SELECT val, diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sql index eb90394883..3703079860 100644 --- a/src/test/regress/sql/numeric.sql +++ b/src/test/regress/sql/numeric.sql @@ -939,6 +939,13 @@ SELECT val, to_char(val::float4, '9.999EEEE') as float4 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 (VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan')) SELECT val,