Commit b813d143 authored by Tom Lane's avatar Tom Lane

Alter scale selection for NUMERIC division and transcendental functions

so that precision of result is always at least as good as you'd get from
float8 arithmetic (ie, always at least 16 digits of accuracy).  Per
pg_hackers discussion a few days ago.
parent c74c7e60
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
* *
* 1998 Jan Wieck * 1998 Jan Wieck
* *
* $Header: /cvsroot/pgsql/src/backend/utils/adt/numeric.c,v 1.54 2002/09/18 21:35:22 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/utils/adt/numeric.c,v 1.55 2002/10/02 19:21:26 tgl Exp $
* *
* ---------- * ----------
*/ */
...@@ -88,7 +88,7 @@ typedef struct NumericVar ...@@ -88,7 +88,7 @@ typedef struct NumericVar
* Local data * Local data
* ---------- * ----------
*/ */
static int global_rscale = NUMERIC_MIN_RESULT_SCALE; static int global_rscale = 0;
/* ---------- /* ----------
* Some preinitialized variables we need often * Some preinitialized variables we need often
...@@ -106,6 +106,18 @@ static NumericDigit const_two_data[1] = {2}; ...@@ -106,6 +106,18 @@ static NumericDigit const_two_data[1] = {2};
static NumericVar const_two = static NumericVar const_two =
{1, 0, 0, 0, NUMERIC_POS, NULL, const_two_data}; {1, 0, 0, 0, NUMERIC_POS, NULL, const_two_data};
static NumericDigit const_zero_point_one_data[1] = {1};
static NumericVar const_zero_point_one =
{1, -1, 1, 1, NUMERIC_POS, NULL, const_zero_point_one_data};
static NumericDigit const_zero_point_nine_data[1] = {9};
static NumericVar const_zero_point_nine =
{1, -1, 1, 1, NUMERIC_POS, NULL, const_zero_point_nine_data};
static NumericDigit const_one_point_one_data[2] = {1, 1};
static NumericVar const_one_point_one =
{2, 0, 1, 1, NUMERIC_POS, NULL, const_one_point_one_data};
static NumericVar const_nan = static NumericVar const_nan =
{0, 0, 0, 0, NUMERIC_NAN, NULL, NULL}; {0, 0, 0, 0, NUMERIC_NAN, NULL, NULL};
...@@ -146,6 +158,9 @@ static Numeric make_result(NumericVar *var); ...@@ -146,6 +158,9 @@ static Numeric make_result(NumericVar *var);
static void apply_typmod(NumericVar *var, int32 typmod); static void apply_typmod(NumericVar *var, int32 typmod);
static double numeric_to_double_no_overflow(Numeric num);
static double numericvar_to_double_no_overflow(NumericVar *var);
static int cmp_numerics(Numeric num1, Numeric num2); static int cmp_numerics(Numeric num1, Numeric num2);
static int cmp_var(NumericVar *var1, NumericVar *var2); static int cmp_var(NumericVar *var1, NumericVar *var2);
static void add_var(NumericVar *var1, NumericVar *var2, NumericVar *result); static void add_var(NumericVar *var1, NumericVar *var2, NumericVar *result);
...@@ -477,8 +492,8 @@ numeric_round(PG_FUNCTION_ARGS) ...@@ -477,8 +492,8 @@ numeric_round(PG_FUNCTION_ARGS)
* Limit the scale value to avoid possible overflow in calculations * Limit the scale value to avoid possible overflow in calculations
* below. * below.
*/ */
scale = Min(NUMERIC_MAX_RESULT_SCALE, scale = Max(scale, -NUMERIC_MAX_RESULT_SCALE);
Max(-NUMERIC_MAX_RESULT_SCALE, scale)); scale = Min(scale, NUMERIC_MAX_RESULT_SCALE);
/* /*
* Unpack the argument and round it at the proper digit position * Unpack the argument and round it at the proper digit position
...@@ -563,8 +578,8 @@ numeric_trunc(PG_FUNCTION_ARGS) ...@@ -563,8 +578,8 @@ numeric_trunc(PG_FUNCTION_ARGS)
* Limit the scale value to avoid possible overflow in calculations * Limit the scale value to avoid possible overflow in calculations
* below. * below.
*/ */
scale = Min(NUMERIC_MAX_RESULT_SCALE, scale = Max(scale, -NUMERIC_MAX_RESULT_SCALE);
Max(-NUMERIC_MAX_RESULT_SCALE, scale)); scale = Min(scale, NUMERIC_MAX_RESULT_SCALE);
/* /*
* Unpack the argument and truncate it at the proper digit position * Unpack the argument and truncate it at the proper digit position
...@@ -1190,6 +1205,7 @@ numeric_sqrt(PG_FUNCTION_ARGS) ...@@ -1190,6 +1205,7 @@ numeric_sqrt(PG_FUNCTION_ARGS)
Numeric res; Numeric res;
NumericVar arg; NumericVar arg;
NumericVar result; NumericVar result;
int sweight;
int res_dscale; int res_dscale;
/* /*
...@@ -1199,20 +1215,28 @@ numeric_sqrt(PG_FUNCTION_ARGS) ...@@ -1199,20 +1215,28 @@ numeric_sqrt(PG_FUNCTION_ARGS)
PG_RETURN_NUMERIC(make_result(&const_nan)); PG_RETURN_NUMERIC(make_result(&const_nan));
/* /*
* Unpack the argument, determine the scales like for divide, let * Unpack the argument and determine the scales. We choose a display
* sqrt_var() do the calculation and return the result. * scale to give at least NUMERIC_MIN_SIG_DIGITS significant digits;
* but in any case not less than the input's dscale.
*/ */
init_var(&arg); init_var(&arg);
init_var(&result); init_var(&result);
set_var_from_num(num, &arg); set_var_from_num(num, &arg);
res_dscale = Max(arg.dscale, NUMERIC_MIN_DISPLAY_SCALE); /* Assume the input was normalized, so arg.weight is accurate */
sweight = (arg.weight / 2) - 1;
res_dscale = NUMERIC_MIN_SIG_DIGITS - sweight;
res_dscale = Max(res_dscale, arg.dscale);
res_dscale = Max(res_dscale, NUMERIC_MIN_DISPLAY_SCALE);
res_dscale = Min(res_dscale, NUMERIC_MAX_DISPLAY_SCALE); res_dscale = Min(res_dscale, NUMERIC_MAX_DISPLAY_SCALE);
global_rscale = Max(arg.rscale, NUMERIC_MIN_RESULT_SCALE);
global_rscale = Max(global_rscale, res_dscale + 4);
global_rscale = Min(global_rscale, NUMERIC_MAX_RESULT_SCALE);
global_rscale = res_dscale + 8;
/*
* Let sqrt_var() do the calculation and return the result.
*/
sqrt_var(&arg, &result); sqrt_var(&arg, &result);
result.dscale = res_dscale; result.dscale = res_dscale;
...@@ -1240,6 +1264,7 @@ numeric_exp(PG_FUNCTION_ARGS) ...@@ -1240,6 +1264,7 @@ numeric_exp(PG_FUNCTION_ARGS)
NumericVar arg; NumericVar arg;
NumericVar result; NumericVar result;
int res_dscale; int res_dscale;
double val;
/* /*
* Handle NaN * Handle NaN
...@@ -1248,18 +1273,35 @@ numeric_exp(PG_FUNCTION_ARGS) ...@@ -1248,18 +1273,35 @@ numeric_exp(PG_FUNCTION_ARGS)
PG_RETURN_NUMERIC(make_result(&const_nan)); PG_RETURN_NUMERIC(make_result(&const_nan));
/* /*
* Same procedure like for sqrt(). * Unpack the argument and determine the scales. We choose a display
* scale to give at least NUMERIC_MIN_SIG_DIGITS significant digits;
* but in any case not less than the input's dscale.
*/ */
init_var(&arg); init_var(&arg);
init_var(&result); init_var(&result);
set_var_from_num(num, &arg); set_var_from_num(num, &arg);
res_dscale = Max(arg.dscale, NUMERIC_MIN_DISPLAY_SCALE); /* convert input to float8, ignoring overflow */
val = numeric_to_double_no_overflow(num);
/* log10(result) = num * log10(e), so this is approximately the weight: */
val *= 0.434294481903252;
/* limit to something that won't cause integer overflow */
val = Max(val, -NUMERIC_MAX_RESULT_SCALE);
val = Min(val, NUMERIC_MAX_RESULT_SCALE);
res_dscale = NUMERIC_MIN_SIG_DIGITS - (int) val;
res_dscale = Max(res_dscale, arg.dscale);
res_dscale = Max(res_dscale, NUMERIC_MIN_DISPLAY_SCALE);
res_dscale = Min(res_dscale, NUMERIC_MAX_DISPLAY_SCALE); res_dscale = Min(res_dscale, NUMERIC_MAX_DISPLAY_SCALE);
global_rscale = Max(arg.rscale, NUMERIC_MIN_RESULT_SCALE);
global_rscale = Max(global_rscale, res_dscale + 4);
global_rscale = Min(global_rscale, NUMERIC_MAX_RESULT_SCALE);
global_rscale = res_dscale + NUMERIC_EXTRA_DIGITS;
/*
* Let exp_var() do the calculation and return the result.
*/
exp_var(&arg, &result); exp_var(&arg, &result);
result.dscale = res_dscale; result.dscale = res_dscale;
...@@ -1294,18 +1336,23 @@ numeric_ln(PG_FUNCTION_ARGS) ...@@ -1294,18 +1336,23 @@ numeric_ln(PG_FUNCTION_ARGS)
if (NUMERIC_IS_NAN(num)) if (NUMERIC_IS_NAN(num))
PG_RETURN_NUMERIC(make_result(&const_nan)); PG_RETURN_NUMERIC(make_result(&const_nan));
/*
* Same procedure like for sqrt()
*/
init_var(&arg); init_var(&arg);
init_var(&result); init_var(&result);
set_var_from_num(num, &arg); set_var_from_num(num, &arg);
res_dscale = Max(arg.dscale, NUMERIC_MIN_DISPLAY_SCALE); if (arg.weight > 0)
res_dscale = NUMERIC_MIN_SIG_DIGITS - (int) log10(arg.weight);
else if (arg.weight < 0)
res_dscale = NUMERIC_MIN_SIG_DIGITS - (int) log10(- arg.weight);
else
res_dscale = NUMERIC_MIN_SIG_DIGITS;
res_dscale = Max(res_dscale, arg.dscale);
res_dscale = Max(res_dscale, NUMERIC_MIN_DISPLAY_SCALE);
res_dscale = Min(res_dscale, NUMERIC_MAX_DISPLAY_SCALE); res_dscale = Min(res_dscale, NUMERIC_MAX_DISPLAY_SCALE);
global_rscale = Max(arg.rscale, NUMERIC_MIN_RESULT_SCALE);
global_rscale = Max(global_rscale, res_dscale + 4); global_rscale = res_dscale + NUMERIC_EXTRA_DIGITS;
global_rscale = Min(global_rscale, NUMERIC_MAX_RESULT_SCALE);
ln_var(&arg, &result); ln_var(&arg, &result);
...@@ -1335,7 +1382,6 @@ numeric_log(PG_FUNCTION_ARGS) ...@@ -1335,7 +1382,6 @@ numeric_log(PG_FUNCTION_ARGS)
NumericVar arg1; NumericVar arg1;
NumericVar arg2; NumericVar arg2;
NumericVar result; NumericVar result;
int res_dscale;
/* /*
* Handle NaN * Handle NaN
...@@ -1344,27 +1390,21 @@ numeric_log(PG_FUNCTION_ARGS) ...@@ -1344,27 +1390,21 @@ numeric_log(PG_FUNCTION_ARGS)
PG_RETURN_NUMERIC(make_result(&const_nan)); PG_RETURN_NUMERIC(make_result(&const_nan));
/* /*
* Initialize things and calculate scales * Initialize things
*/ */
init_var(&arg1); init_var(&arg1);
init_var(&arg2); init_var(&arg2);
init_var(&result); init_var(&result);
set_var_from_num(num1, &arg1); set_var_from_num(num1, &arg1);
set_var_from_num(num2, &arg2); set_var_from_num(num2, &arg2);
res_dscale = Max(arg1.dscale + arg2.dscale, NUMERIC_MIN_DISPLAY_SCALE);
res_dscale = Min(res_dscale, NUMERIC_MAX_DISPLAY_SCALE);
global_rscale = Max(arg1.rscale + arg2.rscale, NUMERIC_MIN_RESULT_SCALE);
global_rscale = Max(global_rscale, res_dscale + 4);
global_rscale = Min(global_rscale, NUMERIC_MAX_RESULT_SCALE);
/* /*
* Call log_var() to compute and return the result * Call log_var() to compute and return the result; note it handles
* rscale/dscale itself.
*/ */
log_var(&arg1, &arg2, &result); log_var(&arg1, &arg2, &result);
result.dscale = res_dscale;
res = make_result(&result); res = make_result(&result);
free_var(&result); free_var(&result);
...@@ -1378,7 +1418,7 @@ numeric_log(PG_FUNCTION_ARGS) ...@@ -1378,7 +1418,7 @@ numeric_log(PG_FUNCTION_ARGS)
/* ---------- /* ----------
* numeric_power() - * numeric_power() -
* *
* Raise m to the power of x * Raise b to the power of x
* ---------- * ----------
*/ */
Datum Datum
...@@ -1390,7 +1430,6 @@ numeric_power(PG_FUNCTION_ARGS) ...@@ -1390,7 +1430,6 @@ numeric_power(PG_FUNCTION_ARGS)
NumericVar arg1; NumericVar arg1;
NumericVar arg2; NumericVar arg2;
NumericVar result; NumericVar result;
int res_dscale;
/* /*
* Handle NaN * Handle NaN
...@@ -1399,27 +1438,21 @@ numeric_power(PG_FUNCTION_ARGS) ...@@ -1399,27 +1438,21 @@ numeric_power(PG_FUNCTION_ARGS)
PG_RETURN_NUMERIC(make_result(&const_nan)); PG_RETURN_NUMERIC(make_result(&const_nan));
/* /*
* Initialize things and calculate scales * Initialize things
*/ */
init_var(&arg1); init_var(&arg1);
init_var(&arg2); init_var(&arg2);
init_var(&result); init_var(&result);
set_var_from_num(num1, &arg1); set_var_from_num(num1, &arg1);
set_var_from_num(num2, &arg2); set_var_from_num(num2, &arg2);
res_dscale = Max(arg1.dscale + arg2.dscale, NUMERIC_MIN_DISPLAY_SCALE);
res_dscale = Min(res_dscale, NUMERIC_MAX_DISPLAY_SCALE);
global_rscale = Max(arg1.rscale + arg2.rscale, NUMERIC_MIN_RESULT_SCALE);
global_rscale = Max(global_rscale, res_dscale + 4);
global_rscale = Min(global_rscale, NUMERIC_MAX_RESULT_SCALE);
/* /*
* Call log_var() to compute and return the result * Call power_var() to compute and return the result; note it handles
* rscale/dscale itself.
*/ */
power_var(&arg1, &arg2, &result); power_var(&arg1, &arg2, &result);
result.dscale = res_dscale;
res = make_result(&result); res = make_result(&result);
free_var(&result); free_var(&result);
...@@ -1640,30 +1673,16 @@ Datum ...@@ -1640,30 +1673,16 @@ Datum
numeric_float8_no_overflow(PG_FUNCTION_ARGS) numeric_float8_no_overflow(PG_FUNCTION_ARGS)
{ {
Numeric num = PG_GETARG_NUMERIC(0); Numeric num = PG_GETARG_NUMERIC(0);
char *tmp;
double val; double val;
char *endptr;
if (NUMERIC_IS_NAN(num)) if (NUMERIC_IS_NAN(num))
PG_RETURN_FLOAT8(NAN); PG_RETURN_FLOAT8(NAN);
tmp = DatumGetCString(DirectFunctionCall1(numeric_out, val = numeric_to_double_no_overflow(num);
NumericGetDatum(num)));
/* unlike float8in, we ignore ERANGE from strtod */
val = strtod(tmp, &endptr);
if (*endptr != '\0')
{
/* shouldn't happen ... */
elog(ERROR, "Bad float8 input format '%s'", tmp);
}
pfree(tmp);
PG_RETURN_FLOAT8(val); PG_RETURN_FLOAT8(val);
} }
Datum Datum
float4_numeric(PG_FUNCTION_ARGS) float4_numeric(PG_FUNCTION_ARGS)
{ {
...@@ -1939,6 +1958,9 @@ numeric_variance(PG_FUNCTION_ARGS) ...@@ -1939,6 +1958,9 @@ numeric_variance(PG_FUNCTION_ARGS)
init_var(&vsumX2); init_var(&vsumX2);
set_var_from_num(sumX2, &vsumX2); set_var_from_num(sumX2, &vsumX2);
/* set rscale for mul_var calls */
global_rscale = vsumX.rscale * 2;
mul_var(&vsumX, &vsumX, &vsumX); /* now vsumX contains sumX * sumX */ mul_var(&vsumX, &vsumX, &vsumX); /* now vsumX contains sumX * sumX */
mul_var(&vN, &vsumX2, &vsumX2); /* now vsumX2 contains N * sumX2 */ mul_var(&vN, &vsumX2, &vsumX2); /* now vsumX2 contains N * sumX2 */
sub_var(&vsumX2, &vsumX, &vsumX2); /* N * sumX2 - sumX * sumX */ sub_var(&vsumX2, &vsumX, &vsumX2); /* N * sumX2 - sumX * sumX */
...@@ -2018,6 +2040,9 @@ numeric_stddev(PG_FUNCTION_ARGS) ...@@ -2018,6 +2040,9 @@ numeric_stddev(PG_FUNCTION_ARGS)
init_var(&vsumX2); init_var(&vsumX2);
set_var_from_num(sumX2, &vsumX2); set_var_from_num(sumX2, &vsumX2);
/* set rscale for mul_var calls */
global_rscale = vsumX.rscale * 2;
mul_var(&vsumX, &vsumX, &vsumX); /* now vsumX contains sumX * sumX */ mul_var(&vsumX, &vsumX, &vsumX); /* now vsumX contains sumX * sumX */
mul_var(&vN, &vsumX2, &vsumX2); /* now vsumX2 contains N * sumX2 */ mul_var(&vN, &vsumX2, &vsumX2); /* now vsumX2 contains N * sumX2 */
sub_var(&vsumX2, &vsumX, &vsumX2); /* N * sumX2 - sumX * sumX */ sub_var(&vsumX2, &vsumX, &vsumX2); /* N * sumX2 - sumX * sumX */
...@@ -2785,6 +2810,54 @@ apply_typmod(NumericVar *var, int32 typmod) ...@@ -2785,6 +2810,54 @@ apply_typmod(NumericVar *var, int32 typmod)
var->dscale = scale; var->dscale = scale;
} }
/* Convert numeric to float8; if out of range, return +/- HUGE_VAL */
/* Caller should have eliminated the possibility of NAN */
static double
numeric_to_double_no_overflow(Numeric num)
{
char *tmp;
double val;
char *endptr;
tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
NumericGetDatum(num)));
/* unlike float8in, we ignore ERANGE from strtod */
val = strtod(tmp, &endptr);
if (*endptr != '\0')
{
/* shouldn't happen ... */
elog(ERROR, "Bad float8 input format '%s'", tmp);
}
pfree(tmp);
return val;
}
/* As above, but work from a NumericVar */
static double
numericvar_to_double_no_overflow(NumericVar *var)
{
char *tmp;
double val;
char *endptr;
tmp = get_str_from_var(var, var->dscale);
/* unlike float8in, we ignore ERANGE from strtod */
val = strtod(tmp, &endptr);
if (*endptr != '\0')
{
/* shouldn't happen ... */
elog(ERROR, "Bad float8 input format '%s'", tmp);
}
pfree(tmp);
return val;
}
/* ---------- /* ----------
* cmp_var() - * cmp_var() -
...@@ -3073,7 +3146,7 @@ sub_var(NumericVar *var1, NumericVar *var2, NumericVar *result) ...@@ -3073,7 +3146,7 @@ sub_var(NumericVar *var1, NumericVar *var2, NumericVar *result)
* mul_var() - * mul_var() -
* *
* Multiplication on variable level. Product of var1 * var2 is stored * Multiplication on variable level. Product of var1 * var2 is stored
* in result. * in result. Accuracy of result is determined by global_rscale.
* ---------- * ----------
*/ */
static void static void
...@@ -3158,7 +3231,8 @@ mul_var(NumericVar *var1, NumericVar *var2, NumericVar *result) ...@@ -3158,7 +3231,8 @@ mul_var(NumericVar *var1, NumericVar *var2, NumericVar *result)
/* ---------- /* ----------
* div_var() - * div_var() -
* *
* Division on variable level. * Division on variable level. Accuracy of result is determined by
* global_rscale.
* ---------- * ----------
*/ */
static void static void
...@@ -3370,33 +3444,69 @@ div_var(NumericVar *var1, NumericVar *var2, NumericVar *result) ...@@ -3370,33 +3444,69 @@ div_var(NumericVar *var1, NumericVar *var2, NumericVar *result)
static int static int
select_div_scale(NumericVar *var1, NumericVar *var2) select_div_scale(NumericVar *var1, NumericVar *var2)
{ {
int weight1,
weight2,
qweight,
i;
NumericDigit firstdigit1,
firstdigit2;
int res_dscale; int res_dscale;
int res_rscale; int res_rscale;
/* ---------- /*
* The result scale of a division isn't specified in any * The result scale of a division isn't specified in any SQL standard.
* SQL standard. For Postgres it is the following (where * For PostgreSQL we select a display scale that will give at least
* SR, DR are the result- and display-scales of the returned * NUMERIC_MIN_SIG_DIGITS significant digits, so that numeric gives a
* value, S1, D1, S2 and D2 are the scales of the two arguments, * result no less accurate than float8; but use a scale not less than
* The minimum and maximum scales are compile time options from * either input's display scale.
* numeric.h):
*
* DR = Min(Max(D1 + D2, MIN_DISPLAY_SCALE), MAX_DISPLAY_SCALE)
* SR = Min(Max(Max(S1 + S2, DR + 4), MIN_RESULT_SCALE), MAX_RESULT_SCALE)
* *
* By default, any result is computed with a minimum of 34 digits * The result scale is NUMERIC_EXTRA_DIGITS more than the display scale,
* after the decimal point or at least with 4 digits more than * to provide some guard digits in the calculation.
* displayed.
* ----------
*/ */
res_dscale = var1->dscale + var2->dscale;
/* Get the actual (normalized) weight and first digit of each input */
weight1 = 0; /* values to use if var1 is zero */
firstdigit1 = 0;
for (i = 0; i < var1->ndigits; i++)
{
firstdigit1 = var1->digits[i];
if (firstdigit1 != 0)
{
weight1 = var1->weight - i;
break;
}
}
weight2 = 0; /* values to use if var2 is zero */
firstdigit2 = 0;
for (i = 0; i < var2->ndigits; i++)
{
firstdigit2 = var2->digits[i];
if (firstdigit2 != 0)
{
weight2 = var2->weight - i;
break;
}
}
/*
* Estimate weight of quotient. If the two first digits are equal,
* we can't be sure, but assume that var1 is less than var2.
*/
qweight = weight1 - weight2;
if (firstdigit1 <= firstdigit2)
qweight--;
/* Select display scale */
res_dscale = NUMERIC_MIN_SIG_DIGITS - qweight;
res_dscale = Max(res_dscale, var1->dscale);
res_dscale = Max(res_dscale, var2->dscale);
res_dscale = Max(res_dscale, NUMERIC_MIN_DISPLAY_SCALE); res_dscale = Max(res_dscale, NUMERIC_MIN_DISPLAY_SCALE);
res_dscale = Min(res_dscale, NUMERIC_MAX_DISPLAY_SCALE); res_dscale = Min(res_dscale, NUMERIC_MAX_DISPLAY_SCALE);
res_rscale = var1->rscale + var2->rscale; /* Select result scale */
res_rscale = Max(res_rscale, res_dscale + 4); res_rscale = res_dscale + NUMERIC_EXTRA_DIGITS;
res_rscale = Max(res_rscale, NUMERIC_MIN_RESULT_SCALE);
res_rscale = Min(res_rscale, NUMERIC_MAX_RESULT_SCALE);
global_rscale = res_rscale; global_rscale = res_rscale;
return res_dscale; return res_dscale;
...@@ -3503,7 +3613,7 @@ floor_var(NumericVar *var, NumericVar *result) ...@@ -3503,7 +3613,7 @@ floor_var(NumericVar *var, NumericVar *result)
/* ---------- /* ----------
* sqrt_var() - * sqrt_var() -
* *
* Compute the square root of x using Newtons algorithm * Compute the square root of x using Newton's algorithm
* ---------- * ----------
*/ */
static void static void
...@@ -3526,6 +3636,7 @@ sqrt_var(NumericVar *arg, NumericVar *result) ...@@ -3526,6 +3636,7 @@ sqrt_var(NumericVar *arg, NumericVar *result)
set_var_from_var(&const_zero, result); set_var_from_var(&const_zero, result);
result->rscale = res_rscale; result->rscale = res_rscale;
result->sign = NUMERIC_POS; result->sign = NUMERIC_POS;
global_rscale = save_global_rscale;
return; return;
} }
...@@ -3536,8 +3647,8 @@ sqrt_var(NumericVar *arg, NumericVar *result) ...@@ -3536,8 +3647,8 @@ sqrt_var(NumericVar *arg, NumericVar *result)
init_var(&tmp_val); init_var(&tmp_val);
init_var(&last_val); init_var(&last_val);
/* Copy arg in case it is the same var as result */
set_var_from_var(arg, &tmp_arg); set_var_from_var(arg, &tmp_arg);
set_var_from_var(result, &last_val);
/* /*
* Initialize the result to the first guess * Initialize the result to the first guess
...@@ -3553,6 +3664,8 @@ sqrt_var(NumericVar *arg, NumericVar *result) ...@@ -3553,6 +3664,8 @@ sqrt_var(NumericVar *arg, NumericVar *result)
result->rscale = res_rscale; result->rscale = res_rscale;
result->sign = NUMERIC_POS; result->sign = NUMERIC_POS;
set_var_from_var(result, &last_val);
for (;;) for (;;)
{ {
div_var(&tmp_arg, result, &tmp_val); div_var(&tmp_arg, result, &tmp_val);
...@@ -3590,6 +3703,7 @@ exp_var(NumericVar *arg, NumericVar *result) ...@@ -3590,6 +3703,7 @@ exp_var(NumericVar *arg, NumericVar *result)
NumericVar ni; NumericVar ni;
int d; int d;
int i; int i;
int xintval;
int ndiv2 = 0; int ndiv2 = 0;
bool xneg = FALSE; bool xneg = FALSE;
int save_global_rscale; int save_global_rscale;
...@@ -3608,32 +3722,42 @@ exp_var(NumericVar *arg, NumericVar *result) ...@@ -3608,32 +3722,42 @@ exp_var(NumericVar *arg, NumericVar *result)
x.sign = NUMERIC_POS; x.sign = NUMERIC_POS;
} }
save_global_rscale = global_rscale; /* Select an appropriate scale for internal calculation */
global_rscale = 0; xintval = 0;
for (i = x.weight, d = 0; i >= 0; i--, d++) for (i = x.weight, d = 0; i >= 0; i--, d++)
{ {
global_rscale *= 10; xintval *= 10;
if (d < x.ndigits) if (d < x.ndigits)
global_rscale += x.digits[d]; xintval += x.digits[d];
if (global_rscale >= 1000) if (xintval >= NUMERIC_MAX_RESULT_SCALE)
elog(ERROR, "argument for EXP() too big"); elog(ERROR, "argument for EXP() too big");
} }
global_rscale = global_rscale / 2 + save_global_rscale + 8; save_global_rscale = global_rscale;
global_rscale += xintval / 2 + 8;
while (cmp_var(&x, &const_one) > 0) /* Reduce input into range 0 <= x <= 0.1 */
while (cmp_var(&x, &const_zero_point_one) > 0)
{ {
ndiv2++; ndiv2++;
global_rscale++; global_rscale++;
div_var(&x, &const_two, &x); div_var(&x, &const_two, &x);
} }
/*
* Use the Taylor series
*
* exp(x) = 1 + x + x^2/2! + x^3/3! + ...
*
* Given the limited range of x, this should converge reasonably quickly.
* We run the series until the terms fall below the global_rscale limit.
*/
add_var(&const_one, &x, result); add_var(&const_one, &x, result);
set_var_from_var(&x, &xpow); set_var_from_var(&x, &xpow);
set_var_from_var(&const_one, &ifac); set_var_from_var(&const_one, &ifac);
set_var_from_var(&const_one, &ni); set_var_from_var(&const_one, &ni);
for (i = 2;; i++) for (;;)
{ {
add_var(&ni, &const_one, &ni); add_var(&ni, &const_one, &ni);
mul_var(&xpow, &x, &xpow); mul_var(&xpow, &x, &xpow);
...@@ -3646,10 +3770,13 @@ exp_var(NumericVar *arg, NumericVar *result) ...@@ -3646,10 +3770,13 @@ exp_var(NumericVar *arg, NumericVar *result)
add_var(result, &elem, result); add_var(result, &elem, result);
} }
/* Compensate for argument range reduction */
while (ndiv2-- > 0) while (ndiv2-- > 0)
mul_var(result, result, result); mul_var(result, result, result);
/* Compensate for input sign, and round to caller's global_rscale */
global_rscale = save_global_rscale; global_rscale = save_global_rscale;
if (xneg) if (xneg)
div_var(&const_one, result, result); div_var(&const_one, result, result);
else else
...@@ -3679,7 +3806,6 @@ ln_var(NumericVar *arg, NumericVar *result) ...@@ -3679,7 +3806,6 @@ ln_var(NumericVar *arg, NumericVar *result)
NumericVar ni; NumericVar ni;
NumericVar elem; NumericVar elem;
NumericVar fact; NumericVar fact;
int i;
int save_global_rscale; int save_global_rscale;
if (cmp_var(arg, &const_zero) <= 0) if (cmp_var(arg, &const_zero) <= 0)
...@@ -3697,18 +3823,31 @@ ln_var(NumericVar *arg, NumericVar *result) ...@@ -3697,18 +3823,31 @@ ln_var(NumericVar *arg, NumericVar *result)
set_var_from_var(&const_two, &fact); set_var_from_var(&const_two, &fact);
set_var_from_var(arg, &x); set_var_from_var(arg, &x);
while (cmp_var(&x, &const_two) >= 0) /* Reduce input into range 0.9 < x < 1.1 */
while (cmp_var(&x, &const_zero_point_nine) <= 0)
{ {
global_rscale++;
sqrt_var(&x, &x); sqrt_var(&x, &x);
mul_var(&fact, &const_two, &fact); mul_var(&fact, &const_two, &fact);
} }
set_var_from_str("0.5", &elem); while (cmp_var(&x, &const_one_point_one) >= 0)
while (cmp_var(&x, &elem) <= 0)
{ {
global_rscale++;
sqrt_var(&x, &x); sqrt_var(&x, &x);
mul_var(&fact, &const_two, &fact); mul_var(&fact, &const_two, &fact);
} }
/*
* We use the Taylor series for 0.5 * ln((1+z)/(1-z)),
*
* z + z^3/3 + z^5/5 + ...
*
* where z = (x-1)/(x+1) is in the range (approximately) -0.053 .. 0.048
* due to the above range-reduction of x.
*
* The convergence of this is not as fast as one would like, but is
* tolerable given that z is small.
*/
sub_var(&x, &const_one, result); sub_var(&x, &const_one, result);
add_var(&x, &const_one, &elem); add_var(&x, &const_one, &elem);
div_var(result, &elem, result); div_var(result, &elem, result);
...@@ -3717,19 +3856,21 @@ ln_var(NumericVar *arg, NumericVar *result) ...@@ -3717,19 +3856,21 @@ ln_var(NumericVar *arg, NumericVar *result)
set_var_from_var(&const_one, &ni); set_var_from_var(&const_one, &ni);
for (i = 2;; i++) for (;;)
{ {
add_var(&ni, &const_two, &ni); add_var(&ni, &const_two, &ni);
mul_var(&xx, &x, &xx); mul_var(&xx, &x, &xx);
div_var(&xx, &ni, &elem); div_var(&xx, &ni, &elem);
if (cmp_var(&elem, &const_zero) == 0) if (elem.ndigits == 0)
break; break;
add_var(result, &elem, result); add_var(result, &elem, result);
} }
/* Compensate for argument range reduction, round to caller's rscale */
global_rscale = save_global_rscale; global_rscale = save_global_rscale;
mul_var(result, &fact, result); mul_var(result, &fact, result);
free_var(&x); free_var(&x);
...@@ -3743,7 +3884,9 @@ ln_var(NumericVar *arg, NumericVar *result) ...@@ -3743,7 +3884,9 @@ ln_var(NumericVar *arg, NumericVar *result)
/* ---------- /* ----------
* log_var() - * log_var() -
* *
* Compute the logarithm of x in a given base * Compute the logarithm of num in a given base.
*
* Note: this routine chooses rscale and dscale of the result.
* ---------- * ----------
*/ */
static void static void
...@@ -3751,19 +3894,43 @@ log_var(NumericVar *base, NumericVar *num, NumericVar *result) ...@@ -3751,19 +3894,43 @@ log_var(NumericVar *base, NumericVar *num, NumericVar *result)
{ {
NumericVar ln_base; NumericVar ln_base;
NumericVar ln_num; NumericVar ln_num;
int save_global_rscale = global_rscale;
global_rscale += 8; int res_dscale;
init_var(&ln_base); init_var(&ln_base);
init_var(&ln_num); init_var(&ln_num);
/* Set scale for ln() calculations */
if (num->weight > 0)
res_dscale = NUMERIC_MIN_SIG_DIGITS - (int) log10(num->weight);
else if (num->weight < 0)
res_dscale = NUMERIC_MIN_SIG_DIGITS - (int) log10(- num->weight);
else
res_dscale = NUMERIC_MIN_SIG_DIGITS;
res_dscale = Max(res_dscale, base->dscale);
res_dscale = Max(res_dscale, num->dscale);
res_dscale = Max(res_dscale, NUMERIC_MIN_DISPLAY_SCALE);
res_dscale = Min(res_dscale, NUMERIC_MAX_DISPLAY_SCALE);
global_rscale = res_dscale + 8;
/* Form natural logarithms */
ln_var(base, &ln_base); ln_var(base, &ln_base);
ln_var(num, &ln_num); ln_var(num, &ln_num);
global_rscale -= 8; ln_base.dscale = res_dscale;
ln_num.dscale = res_dscale;
/* Select scale for division result */
res_dscale = select_div_scale(&ln_num, &ln_base);
div_var(&ln_num, &ln_base, result); div_var(&ln_num, &ln_base, result);
result->dscale = res_dscale;
global_rscale = save_global_rscale;
free_var(&ln_num); free_var(&ln_num);
free_var(&ln_base); free_var(&ln_base);
} }
...@@ -3773,6 +3940,8 @@ log_var(NumericVar *base, NumericVar *num, NumericVar *result) ...@@ -3773,6 +3940,8 @@ log_var(NumericVar *base, NumericVar *num, NumericVar *result)
* power_var() - * power_var() -
* *
* Raise base to the power of exp * Raise base to the power of exp
*
* Note: this routine chooses rscale and dscale of the result.
* ---------- * ----------
*/ */
static void static void
...@@ -3780,24 +3949,64 @@ power_var(NumericVar *base, NumericVar *exp, NumericVar *result) ...@@ -3780,24 +3949,64 @@ power_var(NumericVar *base, NumericVar *exp, NumericVar *result)
{ {
NumericVar ln_base; NumericVar ln_base;
NumericVar ln_num; NumericVar ln_num;
int save_global_rscale; int save_global_rscale = global_rscale;
int res_dscale;
save_global_rscale = global_rscale; double val;
global_rscale += global_rscale / 3 + 8;
init_var(&ln_base); init_var(&ln_base);
init_var(&ln_num); init_var(&ln_num);
/* Set scale for ln() calculation --- need extra accuracy here */
if (base->weight > 0)
res_dscale = NUMERIC_MIN_SIG_DIGITS*2 - (int) log10(base->weight);
else if (base->weight < 0)
res_dscale = NUMERIC_MIN_SIG_DIGITS*2 - (int) log10(- base->weight);
else
res_dscale = NUMERIC_MIN_SIG_DIGITS*2;
res_dscale = Max(res_dscale, base->dscale * 2);
res_dscale = Max(res_dscale, exp->dscale * 2);
res_dscale = Max(res_dscale, NUMERIC_MIN_DISPLAY_SCALE);
res_dscale = Min(res_dscale, NUMERIC_MAX_DISPLAY_SCALE);
global_rscale = res_dscale + 8;
ln_var(base, &ln_base); ln_var(base, &ln_base);
ln_base.dscale = res_dscale;
mul_var(&ln_base, exp, &ln_num); mul_var(&ln_base, exp, &ln_num);
global_rscale = save_global_rscale; ln_num.dscale = res_dscale;
/* Set scale for exp() */
/* convert input to float8, ignoring overflow */
val = numericvar_to_double_no_overflow(&ln_num);
/* log10(result) = num * log10(e), so this is approximately the weight: */
val *= 0.434294481903252;
/* limit to something that won't cause integer overflow */
val = Max(val, -NUMERIC_MAX_RESULT_SCALE);
val = Min(val, NUMERIC_MAX_RESULT_SCALE);
res_dscale = NUMERIC_MIN_SIG_DIGITS - (int) val;
res_dscale = Max(res_dscale, base->dscale);
res_dscale = Max(res_dscale, exp->dscale);
res_dscale = Max(res_dscale, NUMERIC_MIN_DISPLAY_SCALE);
res_dscale = Min(res_dscale, NUMERIC_MAX_DISPLAY_SCALE);
global_rscale = res_dscale + 8;
exp_var(&ln_num, result); exp_var(&ln_num, result);
result->dscale = res_dscale;
global_rscale = save_global_rscale;
free_var(&ln_num); free_var(&ln_num);
free_var(&ln_base); free_var(&ln_base);
} }
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
* *
* 1998 Jan Wieck * 1998 Jan Wieck
* *
* $Header: /cvsroot/pgsql/src/include/utils/numeric.h,v 1.15 2001/11/05 17:46:36 momjian Exp $ * $Id: numeric.h,v 1.16 2002/10/02 19:21:26 tgl Exp $
* *
* ---------- * ----------
*/ */
...@@ -13,29 +13,36 @@ ...@@ -13,29 +13,36 @@
#ifndef _PG_NUMERIC_H_ #ifndef _PG_NUMERIC_H_
#define _PG_NUMERIC_H_ #define _PG_NUMERIC_H_
/* ---------- /*
* The hardcoded limits and defaults of the numeric data type * Hardcoded precision limit - arbitrary, but must be small enough that
* ---------- * dscale values will fit in 14 bits.
*/ */
#define NUMERIC_MAX_PRECISION 1000 #define NUMERIC_MAX_PRECISION 1000
#define NUMERIC_DEFAULT_PRECISION 30
#define NUMERIC_DEFAULT_SCALE 6
/*
/* ----------
* Internal limits on the scales chosen for calculation results * Internal limits on the scales chosen for calculation results
* ----------
*/ */
#define NUMERIC_MAX_DISPLAY_SCALE NUMERIC_MAX_PRECISION #define NUMERIC_MAX_DISPLAY_SCALE NUMERIC_MAX_PRECISION
#define NUMERIC_MIN_DISPLAY_SCALE (NUMERIC_DEFAULT_SCALE + 4) #define NUMERIC_MIN_DISPLAY_SCALE 0
#define NUMERIC_MAX_RESULT_SCALE (NUMERIC_MAX_PRECISION * 2) #define NUMERIC_MAX_RESULT_SCALE (NUMERIC_MAX_PRECISION * 2)
#define NUMERIC_MIN_RESULT_SCALE (NUMERIC_DEFAULT_PRECISION + 4)
/*
* For inherently inexact calculations such as division and square root,
* we try to get at least this many significant digits; the idea is to
* deliver a result no worse than float8 would.
*/
#define NUMERIC_MIN_SIG_DIGITS 16
/* ---------- /*
* Standard number of extra digits carried internally while doing
* inexact calculations.
*/
#define NUMERIC_EXTRA_DIGITS 4
/*
* Sign values and macros to deal with packing/unpacking n_sign_dscale * Sign values and macros to deal with packing/unpacking n_sign_dscale
* ----------
*/ */
#define NUMERIC_SIGN_MASK 0xC000 #define NUMERIC_SIGN_MASK 0xC000
#define NUMERIC_POS 0x0000 #define NUMERIC_POS 0x0000
...@@ -48,7 +55,7 @@ ...@@ -48,7 +55,7 @@
NUMERIC_SIGN(n) != NUMERIC_NEG) NUMERIC_SIGN(n) != NUMERIC_NEG)
/* ---------- /*
* The Numeric data type stored in the database * The Numeric data type stored in the database
* *
* NOTE: by convention, values in the packed form have been stripped of * NOTE: by convention, values in the packed form have been stripped of
...@@ -56,7 +63,6 @@ ...@@ -56,7 +63,6 @@
* in the last byte, if the number of digits is odd). In particular, * in the last byte, if the number of digits is odd). In particular,
* if the value is zero, there will be no digits at all! The weight is * if the value is zero, there will be no digits at all! The weight is
* arbitrary in that case, but we normally set it to zero. * arbitrary in that case, but we normally set it to zero.
* ----------
*/ */
typedef struct NumericData typedef struct NumericData
{ {
......
...@@ -3,14 +3,14 @@ ...@@ -3,14 +3,14 @@
-- --
SELECT avg(four) AS avg_1 FROM onek; SELECT avg(four) AS avg_1 FROM onek;
avg_1 avg_1
-------------- ---------------------
1.5000000000 1.50000000000000000
(1 row) (1 row)
SELECT avg(a) AS avg_32 FROM aggtest WHERE a < 100; SELECT avg(a) AS avg_32 FROM aggtest WHERE a < 100;
avg_32 avg_32
--------------- --------------------
32.6666666667 32.666666666666667
(1 row) (1 row)
-- In 7.1, avg(float4) is computed using float8 arithmetic. -- In 7.1, avg(float4) is computed using float8 arithmetic.
...@@ -119,8 +119,8 @@ select ten, count(four), sum(DISTINCT four) from onek group by ten; ...@@ -119,8 +119,8 @@ select ten, count(four), sum(DISTINCT four) from onek group by ten;
SELECT newavg(four) AS avg_1 FROM onek; SELECT newavg(four) AS avg_1 FROM onek;
avg_1 avg_1
-------------- ---------------------
1.5000000000 1.50000000000000000
(1 row) (1 row)
SELECT newsum(four) AS sum_1500 FROM onek; SELECT newsum(four) AS sum_1500 FROM onek;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment