postgresql/src/backend/utils/adt/numeric.c

10222 lines
247 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* numeric.c
* An exact numeric data type for the Postgres database system
*
* Original coding 1998, Jan Wieck. Heavily revised 2003, Tom Lane.
*
* Many of the algorithmic ideas are borrowed from David M. Smith's "FM"
* multiple-precision math library, most recently published as Algorithm
* 786: Multiple-Precision Complex Arithmetic and Functions, ACM
* Transactions on Mathematical Software, Vol. 24, No. 4, December 1998,
* pages 359-367.
*
* Copyright (c) 1998-2020, PostgreSQL Global Development Group
*
* IDENTIFICATION
2010-09-20 22:08:53 +02:00
* src/backend/utils/adt/numeric.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <ctype.h>
#include <float.h>
#include <limits.h>
#include <math.h>
#include "catalog/pg_type.h"
#include "common/hashfn.h"
#include "common/int.h"
#include "funcapi.h"
#include "lib/hyperloglog.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "nodes/supportnodes.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/float.h"
#include "utils/guc.h"
2000-06-15 05:33:12 +02:00
#include "utils/int8.h"
#include "utils/numeric.h"
#include "utils/sortsupport.h"
/* ----------
* Uncomment the following to enable compilation of dump_numeric()
* and dump_var() and to get a dump of any result produced by make_result().
* ----------
#define NUMERIC_DEBUG
*/
/* ----------
* Local data types
*
* Numeric values are represented in a base-NBASE floating point format.
* Each "digit" ranges from 0 to NBASE-1. The type NumericDigit is signed
* and wide enough to store a digit. We assume that NBASE*NBASE can fit in
* an int. Although the purely calculational routines could handle any even
* NBASE that's less than sqrt(INT_MAX), in practice we are only interested
* in NBASE a power of ten, so that I/O conversions and decimal rounding
* are easy. Also, it's actually more efficient if NBASE is rather less than
* sqrt(INT_MAX), so that there is "headroom" for mul_var and div_var_fast to
* postpone processing carries.
*
* Values of NBASE other than 10000 are considered of historical interest only
* and are no longer supported in any sense; no mechanism exists for the client
* to discover the base, so every client supporting binary mode expects the
* base-10000 format. If you plan to change this, also note the numeric
* abbreviation code, which assumes NBASE=10000.
* ----------
*/
#if 0
#define NBASE 10
#define HALF_NBASE 5
#define DEC_DIGITS 1 /* decimal digits per NBASE digit */
#define MUL_GUARD_DIGITS 4 /* these are measured in NBASE digits */
#define DIV_GUARD_DIGITS 8
typedef signed char NumericDigit;
#endif
#if 0
#define NBASE 100
#define HALF_NBASE 50
#define DEC_DIGITS 2 /* decimal digits per NBASE digit */
#define MUL_GUARD_DIGITS 3 /* these are measured in NBASE digits */
#define DIV_GUARD_DIGITS 6
typedef signed char NumericDigit;
#endif
#if 1
#define NBASE 10000
#define HALF_NBASE 5000
#define DEC_DIGITS 4 /* decimal digits per NBASE digit */
#define MUL_GUARD_DIGITS 2 /* these are measured in NBASE digits */
#define DIV_GUARD_DIGITS 4
typedef int16 NumericDigit;
#endif
/*
* The Numeric type as stored on disk.
*
* If the high bits of the first word of a NumericChoice (n_header, or
* n_short.n_header, or n_long.n_sign_dscale) are NUMERIC_SHORT, then the
* numeric follows the NumericShort format; if they are NUMERIC_POS or
* NUMERIC_NEG, it follows the NumericLong format. If they are NUMERIC_NAN,
* it is a NaN. We currently always store a NaN using just two bytes (i.e.
* only n_header), but previous releases used only the NumericLong format,
* so we might find 4-byte NaNs on disk if a database has been migrated using
* pg_upgrade. In either case, when the high bits indicate a NaN, the
* remaining bits are never examined. Currently, we always initialize these
* to zero, but it might be possible to use them for some other purpose in
* the future.
*
* In the NumericShort format, the remaining 14 bits of the header word
* (n_short.n_header) are allocated as follows: 1 for sign (positive or
* negative), 6 for dynamic scale, and 7 for weight. In practice, most
* commonly-encountered values can be represented this way.
*
* In the NumericLong format, the remaining 14 bits of the header word
* (n_long.n_sign_dscale) represent the display scale; and the weight is
* stored separately in n_weight.
*
* NOTE: by convention, values in the packed form have been stripped of
* all leading and trailing zero digits (where a "digit" is of base NBASE).
* In particular, 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.
*/
struct NumericShort
{
uint16 n_header; /* Sign + display scale + weight */
NumericDigit n_data[FLEXIBLE_ARRAY_MEMBER]; /* Digits */
};
struct NumericLong
{
uint16 n_sign_dscale; /* Sign + display scale */
int16 n_weight; /* Weight of 1st digit */
NumericDigit n_data[FLEXIBLE_ARRAY_MEMBER]; /* Digits */
};
union NumericChoice
{
uint16 n_header; /* Header word */
2011-04-10 17:42:00 +02:00
struct NumericLong n_long; /* Long form (4-byte header) */
struct NumericShort n_short; /* Short form (2-byte header) */
};
struct NumericData
{
int32 vl_len_; /* varlena header (do not touch directly!) */
2011-04-10 17:42:00 +02:00
union NumericChoice choice; /* choice of format */
};
/*
* Interpretation of high bits.
*/
#define NUMERIC_SIGN_MASK 0xC000
#define NUMERIC_POS 0x0000
#define NUMERIC_NEG 0x4000
#define NUMERIC_SHORT 0x8000
#define NUMERIC_NAN 0xC000
#define NUMERIC_FLAGBITS(n) ((n)->choice.n_header & NUMERIC_SIGN_MASK)
#define NUMERIC_IS_NAN(n) (NUMERIC_FLAGBITS(n) == NUMERIC_NAN)
#define NUMERIC_IS_SHORT(n) (NUMERIC_FLAGBITS(n) == NUMERIC_SHORT)
#define NUMERIC_HDRSZ (VARHDRSZ + sizeof(uint16) + sizeof(int16))
2011-04-10 17:42:00 +02:00
#define NUMERIC_HDRSZ_SHORT (VARHDRSZ + sizeof(uint16))
/*
* If the flag bits are NUMERIC_SHORT or NUMERIC_NAN, we want the short header;
* otherwise, we want the long one. Instead of testing against each value, we
* can just look at the high bit, for a slight efficiency gain.
*/
#define NUMERIC_HEADER_IS_SHORT(n) (((n)->choice.n_header & 0x8000) != 0)
#define NUMERIC_HEADER_SIZE(n) \
(VARHDRSZ + sizeof(uint16) + \
(NUMERIC_HEADER_IS_SHORT(n) ? 0 : sizeof(int16)))
/*
* Short format definitions.
*/
#define NUMERIC_SHORT_SIGN_MASK 0x2000
#define NUMERIC_SHORT_DSCALE_MASK 0x1F80
#define NUMERIC_SHORT_DSCALE_SHIFT 7
#define NUMERIC_SHORT_DSCALE_MAX \
(NUMERIC_SHORT_DSCALE_MASK >> NUMERIC_SHORT_DSCALE_SHIFT)
#define NUMERIC_SHORT_WEIGHT_SIGN_MASK 0x0040
#define NUMERIC_SHORT_WEIGHT_MASK 0x003F
#define NUMERIC_SHORT_WEIGHT_MAX NUMERIC_SHORT_WEIGHT_MASK
#define NUMERIC_SHORT_WEIGHT_MIN (-(NUMERIC_SHORT_WEIGHT_MASK+1))
/*
* Extract sign, display scale, weight.
*/
2011-04-10 17:42:00 +02:00
#define NUMERIC_DSCALE_MASK 0x3FFF
#define NUMERIC_SIGN(n) \
(NUMERIC_IS_SHORT(n) ? \
(((n)->choice.n_short.n_header & NUMERIC_SHORT_SIGN_MASK) ? \
NUMERIC_NEG : NUMERIC_POS) : NUMERIC_FLAGBITS(n))
#define NUMERIC_DSCALE(n) (NUMERIC_HEADER_IS_SHORT((n)) ? \
((n)->choice.n_short.n_header & NUMERIC_SHORT_DSCALE_MASK) \
>> NUMERIC_SHORT_DSCALE_SHIFT \
: ((n)->choice.n_long.n_sign_dscale & NUMERIC_DSCALE_MASK))
#define NUMERIC_WEIGHT(n) (NUMERIC_HEADER_IS_SHORT((n)) ? \
(((n)->choice.n_short.n_header & NUMERIC_SHORT_WEIGHT_SIGN_MASK ? \
~NUMERIC_SHORT_WEIGHT_MASK : 0) \
| ((n)->choice.n_short.n_header & NUMERIC_SHORT_WEIGHT_MASK)) \
: ((n)->choice.n_long.n_weight))
/* ----------
* NumericVar is the format we use for arithmetic. The digit-array part
* is the same as the NumericData storage format, but the header is more
* complex.
*
* The value represented by a NumericVar is determined by the sign, weight,
* ndigits, and digits[] array.
2015-11-14 20:55:38 +01:00
*
* Note: the first digit of a NumericVar's value is assumed to be multiplied
* by NBASE ** weight. Another way to say it is that there are weight+1
* digits before the decimal point. It is possible to have weight < 0.
*
* buf points at the physical start of the palloc'd digit buffer for the
* NumericVar. digits points at the first digit in actual use (the one
* with the specified weight). We normally leave an unused digit or two
* (preset to zeroes) between buf and digits, so that there is room to store
* a carry out of the top digit without reallocating space. We just need to
* decrement digits (and increment weight) to make room for the carry digit.
* (There is no such extra space in a numeric value stored in the database,
* only in a NumericVar in memory.)
*
* If buf is NULL then the digit buffer isn't actually palloc'd and should
* not be freed --- see the constants below for an example.
*
* dscale, or display scale, is the nominal precision expressed as number
* of digits after the decimal point (it must always be >= 0 at present).
* dscale may be more than the number of physically stored fractional digits,
* implying that we have suppressed storage of significant trailing zeroes.
* It should never be less than the number of stored digits, since that would
* imply hiding digits that are present. NOTE that dscale is always expressed
* in *decimal* digits, and so it may correspond to a fractional number of
* base-NBASE digits --- divide by DEC_DIGITS to convert to NBASE digits.
*
* rscale, or result scale, is the target precision for a computation.
* Like dscale it is expressed as number of *decimal* digits after the decimal
* point, and is always >= 0 at present.
* Note that rscale is not stored in variables --- it's figured on-the-fly
* from the dscales of the inputs.
*
2015-11-14 20:55:38 +01:00
* While we consistently use "weight" to refer to the base-NBASE weight of
* a numeric value, it is convenient in some scale-related calculations to
* make use of the base-10 weight (ie, the approximate log10 of the value).
* To avoid confusion, such a decimal-units weight is called a "dweight".
*
* NB: All the variable-level functions are written in a style that makes it
* possible to give one and the same variable as argument and destination.
* This is feasible because the digit buffer is separate from the variable.
* ----------
*/
1999-05-25 18:15:34 +02:00
typedef struct NumericVar
{
int ndigits; /* # of digits in digits[] - can be 0! */
int weight; /* weight of first digit */
2005-10-15 04:49:52 +02:00
int sign; /* NUMERIC_POS, NUMERIC_NEG, or NUMERIC_NAN */
int dscale; /* display scale */
NumericDigit *buf; /* start of palloc'd space for digits[] */
NumericDigit *digits; /* base-NBASE digits */
1999-05-26 00:43:53 +02:00
} NumericVar;
/* ----------
* Data for generate_series
* ----------
*/
typedef struct
{
NumericVar current;
NumericVar stop;
NumericVar step;
} generate_series_numeric_fctx;
/* ----------
* Sort support.
* ----------
*/
typedef struct
{
void *buf; /* buffer for short varlenas */
int64 input_count; /* number of non-null values seen */
bool estimating; /* true if estimating cardinality */
hyperLogLogState abbr_card; /* cardinality estimator */
} NumericSortSupport;
/* ----------
* Fast sum accumulator.
*
* NumericSumAccum is used to implement SUM(), and other standard aggregates
* that track the sum of input values. It uses 32-bit integers to store the
* digits, instead of the normal 16-bit integers (with NBASE=10000). This
* way, we can safely accumulate up to NBASE - 1 values without propagating
* carry, before risking overflow of any of the digits. 'num_uncarried'
* tracks how many values have been accumulated without propagating carry.
*
* Positive and negative values are accumulated separately, in 'pos_digits'
* and 'neg_digits'. This is simpler and faster than deciding whether to add
* or subtract from the current value, for each new value (see sub_var() for
* the logic we avoid by doing this). Both buffers are of same size, and
* have the same weight and scale. In accum_sum_final(), the positive and
* negative sums are added together to produce the final result.
*
* When a new value has a larger ndigits or weight than the accumulator
* currently does, the accumulator is enlarged to accommodate the new value.
* We normally have one zero digit reserved for carry propagation, and that
* is indicated by the 'have_carry_space' flag. When accum_sum_carry() uses
* up the reserved digit, it clears the 'have_carry_space' flag. The next
* call to accum_sum_add() will enlarge the buffer, to make room for the
* extra digit, and set the flag again.
*
* To initialize a new accumulator, simply reset all fields to zeros.
*
* The accumulator does not handle NaNs.
* ----------
*/
typedef struct NumericSumAccum
{
int ndigits;
int weight;
int dscale;
int num_uncarried;
bool have_carry_space;
int32 *pos_digits;
int32 *neg_digits;
} NumericSumAccum;
/*
* We define our own macros for packing and unpacking abbreviated-key
* representations for numeric values in order to avoid depending on
* USE_FLOAT8_BYVAL. The type of abbreviation we use is based only on
* the size of a datum, not the argument-passing convention for float8.
*/
#define NUMERIC_ABBREV_BITS (SIZEOF_DATUM * BITS_PER_BYTE)
#if SIZEOF_DATUM == 8
#define NumericAbbrevGetDatum(X) ((Datum) (X))
#define DatumGetNumericAbbrev(X) ((int64) (X))
#define NUMERIC_ABBREV_NAN NumericAbbrevGetDatum(PG_INT64_MIN)
#else
#define NumericAbbrevGetDatum(X) ((Datum) (X))
#define DatumGetNumericAbbrev(X) ((int32) (X))
#define NUMERIC_ABBREV_NAN NumericAbbrevGetDatum(PG_INT32_MIN)
#endif
/* ----------
* Some preinitialized constants
* ----------
*/
static const NumericDigit const_zero_data[1] = {0};
static const NumericVar const_zero =
{0, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_zero_data};
static const NumericDigit const_one_data[1] = {1};
static const NumericVar const_one =
{1, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_one_data};
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
static const NumericDigit const_zero_point_nine_data[1] = {90};
#elif DEC_DIGITS == 1
static const NumericDigit const_zero_point_nine_data[1] = {9};
#endif
static const NumericVar const_zero_point_nine =
{1, -1, NUMERIC_POS, 1, NULL, (NumericDigit *) const_zero_point_nine_data};
#if DEC_DIGITS == 4
static const NumericDigit const_one_point_one_data[2] = {1, 1000};
#elif DEC_DIGITS == 2
static const NumericDigit const_one_point_one_data[2] = {1, 10};
#elif DEC_DIGITS == 1
static const NumericDigit const_one_point_one_data[2] = {1, 1};
#endif
static const NumericVar const_one_point_one =
{2, 0, NUMERIC_POS, 1, NULL, (NumericDigit *) const_one_point_one_data};
static const NumericVar const_nan =
{0, 0, NUMERIC_NAN, 0, NULL, NULL};
#if DEC_DIGITS == 4
2003-08-04 02:43:34 +02:00
static const int round_powers[4] = {0, 1000, 100, 10};
#endif
/* ----------
* Local functions
* ----------
*/
#ifdef NUMERIC_DEBUG
static void dump_numeric(const char *str, Numeric num);
static void dump_var(const char *str, NumericVar *var);
#else
#define dump_numeric(s,n)
#define dump_var(s,v)
#endif
#define digitbuf_alloc(ndigits) \
((NumericDigit *) palloc((ndigits) * sizeof(NumericDigit)))
#define digitbuf_free(buf) \
do { \
if ((buf) != NULL) \
pfree(buf); \
} while (0)
#define init_var(v) MemSetAligned(v, 0, sizeof(NumericVar))
#define NUMERIC_DIGITS(num) (NUMERIC_HEADER_IS_SHORT(num) ? \
(num)->choice.n_short.n_data : (num)->choice.n_long.n_data)
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
#define NUMERIC_NDIGITS(num) \
((VARSIZE(num) - NUMERIC_HEADER_SIZE(num)) / sizeof(NumericDigit))
#define NUMERIC_CAN_BE_SHORT(scale,weight) \
((scale) <= NUMERIC_SHORT_DSCALE_MAX && \
(weight) <= NUMERIC_SHORT_WEIGHT_MAX && \
(weight) >= NUMERIC_SHORT_WEIGHT_MIN)
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
static void alloc_var(NumericVar *var, int ndigits);
1999-05-26 00:43:53 +02:00
static void free_var(NumericVar *var);
static void zero_var(NumericVar *var);
static const char *set_var_from_str(const char *str, const char *cp,
NumericVar *dest);
1999-05-26 00:43:53 +02:00
static void set_var_from_num(Numeric value, NumericVar *dest);
static void init_var_from_num(Numeric num, NumericVar *dest);
static void set_var_from_var(const NumericVar *value, NumericVar *dest);
static char *get_str_from_var(const NumericVar *var);
static char *get_str_from_var_sci(const NumericVar *var, int rscale);
static Numeric make_result(const NumericVar *var);
static Numeric make_result_opt_error(const NumericVar *var, bool *error);
1999-05-26 00:43:53 +02:00
static void apply_typmod(NumericVar *var, int32 typmod);
static bool numericvar_to_int32(const NumericVar *var, int32 *result);
static bool numericvar_to_int64(const NumericVar *var, int64 *result);
static void int64_to_numericvar(int64 val, NumericVar *var);
#ifdef HAVE_INT128
static bool numericvar_to_int128(const NumericVar *var, int128 *result);
static void int128_to_numericvar(int128 val, NumericVar *var);
#endif
static double numeric_to_double_no_overflow(Numeric num);
static double numericvar_to_double_no_overflow(const NumericVar *var);
static Datum numeric_abbrev_convert(Datum original_datum, SortSupport ssup);
static bool numeric_abbrev_abort(int memtupcount, SortSupport ssup);
static int numeric_fast_cmp(Datum x, Datum y, SortSupport ssup);
static int numeric_cmp_abbrev(Datum x, Datum y, SortSupport ssup);
static Datum numeric_abbrev_convert_var(const NumericVar *var,
NumericSortSupport *nss);
static int cmp_numerics(Numeric num1, Numeric num2);
static int cmp_var(const NumericVar *var1, const NumericVar *var2);
static int cmp_var_common(const NumericDigit *var1digits, int var1ndigits,
int var1weight, int var1sign,
const NumericDigit *var2digits, int var2ndigits,
int var2weight, int var2sign);
static void add_var(const NumericVar *var1, const NumericVar *var2,
NumericVar *result);
static void sub_var(const NumericVar *var1, const NumericVar *var2,
NumericVar *result);
static void mul_var(const NumericVar *var1, const NumericVar *var2,
NumericVar *result,
int rscale);
static void div_var(const NumericVar *var1, const NumericVar *var2,
NumericVar *result,
int rscale, bool round);
static void div_var_fast(const NumericVar *var1, const NumericVar *var2,
NumericVar *result, int rscale, bool round);
static int select_div_scale(const NumericVar *var1, const NumericVar *var2);
static void mod_var(const NumericVar *var1, const NumericVar *var2,
NumericVar *result);
static void div_mod_var(const NumericVar *var1, const NumericVar *var2,
NumericVar *quot, NumericVar *rem);
static void ceil_var(const NumericVar *var, NumericVar *result);
static void floor_var(const NumericVar *var, NumericVar *result);
static void gcd_var(const NumericVar *var1, const NumericVar *var2,
NumericVar *result);
static void sqrt_var(const NumericVar *arg, NumericVar *result, int rscale);
static void exp_var(const NumericVar *arg, NumericVar *result, int rscale);
static int estimate_ln_dweight(const NumericVar *var);
static void ln_var(const NumericVar *arg, NumericVar *result, int rscale);
static void log_var(const NumericVar *base, const NumericVar *num,
NumericVar *result);
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 int cmp_abs(const NumericVar *var1, const NumericVar *var2);
static int cmp_abs_common(const NumericDigit *var1digits, int var1ndigits,
int var1weight,
const NumericDigit *var2digits, int var2ndigits,
int var2weight);
static void add_abs(const NumericVar *var1, const NumericVar *var2,
NumericVar *result);
static void sub_abs(const NumericVar *var1, const NumericVar *var2,
NumericVar *result);
static void round_var(NumericVar *var, int rscale);
static void trunc_var(NumericVar *var, int rscale);
static void strip_var(NumericVar *var);
static void compute_bucket(Numeric operand, Numeric bound1, Numeric bound2,
const NumericVar *count_var, NumericVar *result_var);
static void accum_sum_add(NumericSumAccum *accum, const NumericVar *var1);
static void accum_sum_rescale(NumericSumAccum *accum, const NumericVar *val);
static void accum_sum_carry(NumericSumAccum *accum);
static void accum_sum_reset(NumericSumAccum *accum);
static void accum_sum_final(NumericSumAccum *accum, NumericVar *result);
static void accum_sum_copy(NumericSumAccum *dst, NumericSumAccum *src);
static void accum_sum_combine(NumericSumAccum *accum, NumericSumAccum *accum2);
/* ----------------------------------------------------------------------
*
* Input-, output- and rounding-functions
*
* ----------------------------------------------------------------------
*/
/*
* numeric_in() -
*
* Input function for numeric data type
*/
Datum
numeric_in(PG_FUNCTION_ARGS)
{
char *str = PG_GETARG_CSTRING(0);
#ifdef NOT_USED
Oid typelem = PG_GETARG_OID(1);
#endif
int32 typmod = PG_GETARG_INT32(2);
Numeric res;
const char *cp;
/* Skip leading spaces */
cp = str;
while (*cp)
{
if (!isspace((unsigned char) *cp))
break;
cp++;
}
/*
* Check for NaN
*/
if (pg_strncasecmp(cp, "NaN", 3) == 0)
{
res = make_result(&const_nan);
/* Should be nothing left but spaces */
cp += 3;
while (*cp)
{
if (!isspace((unsigned char) *cp))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
"numeric", str)));
cp++;
}
}
else
{
/*
* Use set_var_from_str() to parse a normal numeric value
*/
NumericVar value;
init_var(&value);
cp = set_var_from_str(str, cp, &value);
/*
* We duplicate a few lines of code here because we would like to
* throw any trailing-junk syntax error before any semantic error
* resulting from apply_typmod. We can't easily fold the two cases
* together because we mustn't apply apply_typmod to a NaN.
*/
while (*cp)
{
if (!isspace((unsigned char) *cp))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
"numeric", str)));
cp++;
}
apply_typmod(&value, typmod);
res = make_result(&value);
free_var(&value);
}
PG_RETURN_NUMERIC(res);
}
/*
* numeric_out() -
*
* Output function for numeric data type
*/
Datum
numeric_out(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
NumericVar x;
char *str;
/*
* Handle NaN
*/
if (NUMERIC_IS_NAN(num))
PG_RETURN_CSTRING(pstrdup("NaN"));
/*
* Get the number in the variable format.
*/
init_var_from_num(num, &x);
str = get_str_from_var(&x);
PG_RETURN_CSTRING(str);
}
/*
* numeric_is_nan() -
*
2011-04-10 17:42:00 +02:00
* Is Numeric value a NaN?
*/
bool
numeric_is_nan(Numeric num)
{
return NUMERIC_IS_NAN(num);
}
/*
* numeric_maximum_size() -
*
2011-04-10 17:42:00 +02:00
* Maximum size of a numeric with given typmod, or -1 if unlimited/unknown.
*/
int32
numeric_maximum_size(int32 typmod)
{
2011-04-10 17:42:00 +02:00
int precision;
int numeric_digits;
if (typmod < (int32) (VARHDRSZ))
return -1;
/* precision (ie, max # of digits) is in upper bits of typmod */
precision = ((typmod - VARHDRSZ) >> 16) & 0xffff;
/*
2011-04-10 17:42:00 +02:00
* This formula computes the maximum number of NumericDigits we could need
* in order to store the specified number of decimal digits. Because the
* weight is stored as a number of NumericDigits rather than a number of
* decimal digits, it's possible that the first NumericDigit will contain
* only a single decimal digit. Thus, the first two decimal digits can
* require two NumericDigits to store, but it isn't until we reach
* DEC_DIGITS + 2 decimal digits that we potentially need a third
* NumericDigit.
*/
numeric_digits = (precision + 2 * (DEC_DIGITS - 1)) / DEC_DIGITS;
/*
* In most cases, the size of a numeric will be smaller than the value
* computed below, because the varlena header will typically get toasted
2011-04-10 17:42:00 +02:00
* down to a single byte before being stored on disk, and it may also be
* possible to use a short numeric header. But our job here is to compute
2011-04-10 17:42:00 +02:00
* the worst case.
*/
return NUMERIC_HDRSZ + (numeric_digits * sizeof(NumericDigit));
}
/*
* numeric_out_sci() -
*
* Output function for numeric data type in scientific notation.
*/
char *
numeric_out_sci(Numeric num, int scale)
{
NumericVar x;
char *str;
/*
* Handle NaN
*/
if (NUMERIC_IS_NAN(num))
return pstrdup("NaN");
init_var_from_num(num, &x);
str = get_str_from_var_sci(&x, scale);
return str;
}
/*
* numeric_normalize() -
*
* Output function for numeric data type, suppressing insignificant trailing
* zeroes and then any trailing decimal point. The intent of this is to
* produce strings that are equal if and only if the input numeric values
* compare equal.
*/
char *
numeric_normalize(Numeric num)
{
NumericVar x;
char *str;
int last;
/*
* Handle NaN
*/
if (NUMERIC_IS_NAN(num))
return pstrdup("NaN");
init_var_from_num(num, &x);
str = get_str_from_var(&x);
/* If there's no decimal point, there's certainly nothing to remove. */
if (strchr(str, '.') != NULL)
{
/*
* Back up over trailing fractional zeroes. Since there is a decimal
* point, this loop will terminate safely.
*/
last = strlen(str) - 1;
while (str[last] == '0')
last--;
/* We want to get rid of the decimal point too, if it's now last. */
if (str[last] == '.')
last--;
/* Delete whatever we backed up over. */
str[last + 1] = '\0';
}
return str;
}
/*
* numeric_recv - converts external binary format to numeric
*
* External format is a sequence of int16's:
* ndigits, weight, sign, dscale, NumericDigits.
*/
Datum
numeric_recv(PG_FUNCTION_ARGS)
{
StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
2005-10-15 04:49:52 +02:00
#ifdef NOT_USED
Oid typelem = PG_GETARG_OID(1);
#endif
int32 typmod = PG_GETARG_INT32(2);
NumericVar value;
Numeric res;
int len,
i;
init_var(&value);
len = (uint16) pq_getmsgint(buf, sizeof(uint16));
alloc_var(&value, len);
value.weight = (int16) pq_getmsgint(buf, sizeof(int16));
/* we allow any int16 for weight --- OK? */
value.sign = (uint16) pq_getmsgint(buf, sizeof(uint16));
if (!(value.sign == NUMERIC_POS ||
value.sign == NUMERIC_NEG ||
value.sign == NUMERIC_NAN))
ereport(ERROR,
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
errmsg("invalid sign in external \"numeric\" value")));
value.dscale = (uint16) pq_getmsgint(buf, sizeof(uint16));
if ((value.dscale & NUMERIC_DSCALE_MASK) != value.dscale)
ereport(ERROR,
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
errmsg("invalid scale in external \"numeric\" value")));
for (i = 0; i < len; i++)
{
2003-08-04 02:43:34 +02:00
NumericDigit d = pq_getmsgint(buf, sizeof(NumericDigit));
if (d < 0 || d >= NBASE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
2005-10-15 04:49:52 +02:00
errmsg("invalid digit in external \"numeric\" value")));
value.digits[i] = d;
}
/*
* If the given dscale would hide any digits, truncate those digits away.
* We could alternatively throw an error, but that would take a bunch of
* extra code (about as much as trunc_var involves), and it might cause
* client compatibility issues.
*/
trunc_var(&value, value.dscale);
apply_typmod(&value, typmod);
res = make_result(&value);
free_var(&value);
PG_RETURN_NUMERIC(res);
}
/*
* numeric_send - converts numeric to binary format
*/
Datum
numeric_send(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
NumericVar x;
StringInfoData buf;
int i;
init_var_from_num(num, &x);
pq_begintypsend(&buf);
pq_sendint16(&buf, x.ndigits);
pq_sendint16(&buf, x.weight);
pq_sendint16(&buf, x.sign);
pq_sendint16(&buf, x.dscale);
for (i = 0; i < x.ndigits; i++)
pq_sendint16(&buf, x.digits[i]);
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
}
/*
* numeric_support()
*
* Planner support function for the numeric() length coercion function.
*
* Flatten calls that solely represent increases in allowable precision.
* Scale changes mutate every datum, so they are unoptimizable. Some values,
* e.g. 1E-1001, can only fit into an unconstrained numeric, so a change from
* an unconstrained numeric to any constrained numeric is also unoptimizable.
*/
Datum
numeric_support(PG_FUNCTION_ARGS)
{
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
Node *ret = NULL;
if (IsA(rawreq, SupportRequestSimplify))
{
SupportRequestSimplify *req = (SupportRequestSimplify *) rawreq;
FuncExpr *expr = req->fcall;
Node *typmod;
Assert(list_length(expr->args) >= 2);
typmod = (Node *) lsecond(expr->args);
if (IsA(typmod, Const) &&!((Const *) typmod)->constisnull)
{
Node *source = (Node *) linitial(expr->args);
int32 old_typmod = exprTypmod(source);
int32 new_typmod = DatumGetInt32(((Const *) typmod)->constvalue);
int32 old_scale = (old_typmod - VARHDRSZ) & 0xffff;
int32 new_scale = (new_typmod - VARHDRSZ) & 0xffff;
int32 old_precision = (old_typmod - VARHDRSZ) >> 16 & 0xffff;
int32 new_precision = (new_typmod - VARHDRSZ) >> 16 & 0xffff;
/*
* If new_typmod < VARHDRSZ, the destination is unconstrained;
* that's always OK. If old_typmod >= VARHDRSZ, the source is
* constrained, and we're OK if the scale is unchanged and the
* precision is not decreasing. See further notes in function
* header comment.
*/
if (new_typmod < (int32) VARHDRSZ ||
(old_typmod >= (int32) VARHDRSZ &&
new_scale == old_scale && new_precision >= old_precision))
ret = relabel_to_typmod(source, new_typmod);
}
}
PG_RETURN_POINTER(ret);
}
/*
* numeric() -
*
* This is a special function called by the Postgres database system
* before a value is stored in a tuple's attribute. The precision and
1999-05-25 18:15:34 +02:00
* scale of the attribute have to be applied on the value.
*/
Datum
2007-11-15 22:14:46 +01:00
numeric (PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
int32 typmod = PG_GETARG_INT32(1);
Numeric new;
int32 tmp_typmod;
int precision;
int scale;
int ddigits;
int maxdigits;
NumericVar var;
/*
* Handle NaN
*/
if (NUMERIC_IS_NAN(num))
PG_RETURN_NUMERIC(make_result(&const_nan));
/*
2005-10-15 04:49:52 +02:00
* If the value isn't a valid type modifier, simply return a copy of the
* input value
*/
1999-05-25 18:15:34 +02:00
if (typmod < (int32) (VARHDRSZ))
{
new = (Numeric) palloc(VARSIZE(num));
memcpy(new, num, VARSIZE(num));
PG_RETURN_NUMERIC(new);
}
/*
* Get the precision and scale out of the typmod value
*/
tmp_typmod = typmod - VARHDRSZ;
precision = (tmp_typmod >> 16) & 0xffff;
1999-05-25 18:15:34 +02:00
scale = tmp_typmod & 0xffff;
maxdigits = precision - scale;
/*
* If the number is certainly in bounds and due to the target scale no
2005-10-15 04:49:52 +02:00
* rounding could be necessary, just make a copy of the input and modify
* its scale fields, unless the larger scale forces us to abandon the
2011-04-10 17:42:00 +02:00
* short representation. (Note we assume the existing dscale is
* honest...)
*/
ddigits = (NUMERIC_WEIGHT(num) + 1) * DEC_DIGITS;
if (ddigits <= maxdigits && scale >= NUMERIC_DSCALE(num)
&& (NUMERIC_CAN_BE_SHORT(scale, NUMERIC_WEIGHT(num))
2011-04-10 17:42:00 +02:00
|| !NUMERIC_IS_SHORT(num)))
{
new = (Numeric) palloc(VARSIZE(num));
memcpy(new, num, VARSIZE(num));
if (NUMERIC_IS_SHORT(num))
new->choice.n_short.n_header =
(num->choice.n_short.n_header & ~NUMERIC_SHORT_DSCALE_MASK)
| (scale << NUMERIC_SHORT_DSCALE_SHIFT);
else
new->choice.n_long.n_sign_dscale = NUMERIC_SIGN(new) |
((uint16) scale & NUMERIC_DSCALE_MASK);
PG_RETURN_NUMERIC(new);
}
/*
* We really need to fiddle with things - unpack the number into a
* variable and let apply_typmod() do it.
*/
init_var(&var);
set_var_from_num(num, &var);
apply_typmod(&var, typmod);
new = make_result(&var);
free_var(&var);
PG_RETURN_NUMERIC(new);
}
Datum
numerictypmodin(PG_FUNCTION_ARGS)
{
2007-11-15 22:14:46 +01:00
ArrayType *ta = PG_GETARG_ARRAYTYPE_P(0);
int32 *tl;
int n;
int32 typmod;
tl = ArrayGetIntegerTypmods(ta, &n);
if (n == 2)
{
if (tl[0] < 1 || tl[0] > NUMERIC_MAX_PRECISION)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("NUMERIC precision %d must be between 1 and %d",
tl[0], NUMERIC_MAX_PRECISION)));
if (tl[1] < 0 || tl[1] > tl[0])
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("NUMERIC scale %d must be between 0 and precision %d",
tl[1], tl[0])));
typmod = ((tl[0] << 16) | tl[1]) + VARHDRSZ;
}
else if (n == 1)
{
if (tl[0] < 1 || tl[0] > NUMERIC_MAX_PRECISION)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("NUMERIC precision %d must be between 1 and %d",
tl[0], NUMERIC_MAX_PRECISION)));
/* scale defaults to zero */
typmod = (tl[0] << 16) + VARHDRSZ;
}
else
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2007-11-15 22:14:46 +01:00
errmsg("invalid NUMERIC type modifier")));
typmod = 0; /* keep compiler quiet */
}
PG_RETURN_INT32(typmod);
}
Datum
numerictypmodout(PG_FUNCTION_ARGS)
{
2007-11-15 22:14:46 +01:00
int32 typmod = PG_GETARG_INT32(0);
char *res = (char *) palloc(64);
if (typmod >= 0)
snprintf(res, 64, "(%d,%d)",
((typmod - VARHDRSZ) >> 16) & 0xffff,
(typmod - VARHDRSZ) & 0xffff);
else
*res = '\0';
PG_RETURN_CSTRING(res);
}
/* ----------------------------------------------------------------------
*
* Sign manipulation, rounding and the like
*
* ----------------------------------------------------------------------
*/
Datum
numeric_abs(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
Numeric res;
/*
* Handle NaN
*/
if (NUMERIC_IS_NAN(num))
PG_RETURN_NUMERIC(make_result(&const_nan));
/*
* Do it the easy way directly on the packed format
*/
res = (Numeric) palloc(VARSIZE(num));
memcpy(res, num, VARSIZE(num));
if (NUMERIC_IS_SHORT(num))
res->choice.n_short.n_header =
num->choice.n_short.n_header & ~NUMERIC_SHORT_SIGN_MASK;
else
res->choice.n_long.n_sign_dscale = NUMERIC_POS | NUMERIC_DSCALE(num);
PG_RETURN_NUMERIC(res);
}
Datum
numeric_uminus(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
Numeric res;
/*
* Handle NaN
*/
if (NUMERIC_IS_NAN(num))
PG_RETURN_NUMERIC(make_result(&const_nan));
/*
* Do it the easy way directly on the packed format
*/
res = (Numeric) palloc(VARSIZE(num));
memcpy(res, num, VARSIZE(num));
/*
2005-10-15 04:49:52 +02:00
* The packed format is known to be totally zero digit trimmed always. So
* we can identify a ZERO by the fact that there are no digits at all. Do
2005-10-15 04:49:52 +02:00
* nothing to a zero.
*/
if (NUMERIC_NDIGITS(num) != 0)
{
/* Else, flip the sign */
if (NUMERIC_IS_SHORT(num))
res->choice.n_short.n_header =
num->choice.n_short.n_header ^ NUMERIC_SHORT_SIGN_MASK;
else if (NUMERIC_SIGN(num) == NUMERIC_POS)
res->choice.n_long.n_sign_dscale =
NUMERIC_NEG | NUMERIC_DSCALE(num);
else
res->choice.n_long.n_sign_dscale =
NUMERIC_POS | NUMERIC_DSCALE(num);
}
PG_RETURN_NUMERIC(res);
}
Datum
numeric_uplus(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
Numeric res;
res = (Numeric) palloc(VARSIZE(num));
memcpy(res, num, VARSIZE(num));
PG_RETURN_NUMERIC(res);
}
/*
* numeric_sign() -
*
* returns -1 if the argument is less than 0, 0 if the argument is equal
* to 0, and 1 if the argument is greater than zero.
*/
Datum
numeric_sign(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
Numeric res;
NumericVar result;
/*
* Handle NaN
*/
if (NUMERIC_IS_NAN(num))
PG_RETURN_NUMERIC(make_result(&const_nan));
init_var(&result);
/*
2005-10-15 04:49:52 +02:00
* The packed format is known to be totally zero digit trimmed always. So
* we can identify a ZERO by the fact that there are no digits at all.
*/
if (NUMERIC_NDIGITS(num) == 0)
set_var_from_var(&const_zero, &result);
else
{
/*
2005-10-15 04:49:52 +02:00
* And if there are some, we return a copy of ONE with the sign of our
* argument
*/
set_var_from_var(&const_one, &result);
result.sign = NUMERIC_SIGN(num);
}
res = make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
}
/*
* numeric_round() -
*
* Round a value to have 'scale' digits after the decimal point.
* We allow negative 'scale', implying rounding before the decimal
* point --- Oracle interprets rounding that way.
*/
Datum
numeric_round(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
int32 scale = PG_GETARG_INT32(1);
Numeric res;
NumericVar arg;
/*
* Handle NaN
*/
if (NUMERIC_IS_NAN(num))
PG_RETURN_NUMERIC(make_result(&const_nan));
/*
* Limit the scale value to avoid possible overflow in calculations
*/
scale = Max(scale, -NUMERIC_MAX_RESULT_SCALE);
scale = Min(scale, NUMERIC_MAX_RESULT_SCALE);
/*
* Unpack the argument and round it at the proper digit position
*/
init_var(&arg);
set_var_from_num(num, &arg);
round_var(&arg, scale);
/* We don't allow negative output dscale */
if (scale < 0)
arg.dscale = 0;
/*
* Return the rounded result
*/
res = make_result(&arg);
free_var(&arg);
PG_RETURN_NUMERIC(res);
}
/*
* numeric_trunc() -
*
* Truncate a value to have 'scale' digits after the decimal point.
* We allow negative 'scale', implying a truncation before the decimal
* point --- Oracle interprets truncation that way.
*/
Datum
numeric_trunc(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
int32 scale = PG_GETARG_INT32(1);
1999-05-25 18:15:34 +02:00
Numeric res;
NumericVar arg;
/*
* Handle NaN
*/
if (NUMERIC_IS_NAN(num))
PG_RETURN_NUMERIC(make_result(&const_nan));
/*
* Limit the scale value to avoid possible overflow in calculations
*/
scale = Max(scale, -NUMERIC_MAX_RESULT_SCALE);
scale = Min(scale, NUMERIC_MAX_RESULT_SCALE);
/*
* Unpack the argument and truncate it at the proper digit position
*/
init_var(&arg);
set_var_from_num(num, &arg);
trunc_var(&arg, scale);
/* We don't allow negative output dscale */
if (scale < 0)
arg.dscale = 0;
/*
* Return the truncated result
*/
res = make_result(&arg);
free_var(&arg);
PG_RETURN_NUMERIC(res);
}
/*
* numeric_ceil() -
*
* Return the smallest integer greater than or equal to the argument
*/
Datum
numeric_ceil(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
Numeric res;
NumericVar result;
if (NUMERIC_IS_NAN(num))
PG_RETURN_NUMERIC(make_result(&const_nan));
init_var_from_num(num, &result);
ceil_var(&result, &result);
res = make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
}
/*
* numeric_floor() -
*
* Return the largest integer equal to or less than the argument
*/
Datum
numeric_floor(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
Numeric res;
NumericVar result;
if (NUMERIC_IS_NAN(num))
PG_RETURN_NUMERIC(make_result(&const_nan));
init_var_from_num(num, &result);
floor_var(&result, &result);
res = make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
}
/*
* generate_series_numeric() -
*
* Generate series of numeric.
*/
Datum
generate_series_numeric(PG_FUNCTION_ARGS)
{
return generate_series_step_numeric(fcinfo);
}
Datum
generate_series_step_numeric(PG_FUNCTION_ARGS)
{
generate_series_numeric_fctx *fctx;
FuncCallContext *funcctx;
MemoryContext oldcontext;
if (SRF_IS_FIRSTCALL())
{
Numeric start_num = PG_GETARG_NUMERIC(0);
Numeric stop_num = PG_GETARG_NUMERIC(1);
NumericVar steploc = const_one;
/* handle NaN in start and stop values */
if (NUMERIC_IS_NAN(start_num))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("start value cannot be NaN")));
if (NUMERIC_IS_NAN(stop_num))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("stop value cannot be NaN")));
/* see if we were given an explicit step size */
if (PG_NARGS() == 3)
{
Numeric step_num = PG_GETARG_NUMERIC(2);
if (NUMERIC_IS_NAN(step_num))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("step size cannot be NaN")));
init_var_from_num(step_num, &steploc);
if (cmp_var(&steploc, &const_zero) == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("step size cannot equal zero")));
}
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
/*
* Switch to memory context appropriate for multiple function calls.
*/
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* allocate memory for user context */
fctx = (generate_series_numeric_fctx *)
palloc(sizeof(generate_series_numeric_fctx));
/*
* Use fctx to keep state from call to call. Seed current with the
* original start value. We must copy the start_num and stop_num
* values rather than pointing to them, since we may have detoasted
* them in the per-call context.
*/
init_var(&fctx->current);
init_var(&fctx->stop);
init_var(&fctx->step);
set_var_from_num(start_num, &fctx->current);
set_var_from_num(stop_num, &fctx->stop);
set_var_from_var(&steploc, &fctx->step);
funcctx->user_fctx = fctx;
MemoryContextSwitchTo(oldcontext);
}
/* stuff done on every call of the function */
funcctx = SRF_PERCALL_SETUP();
/*
* Get the saved state and use current state as the result of this
* iteration.
*/
fctx = funcctx->user_fctx;
if ((fctx->step.sign == NUMERIC_POS &&
cmp_var(&fctx->current, &fctx->stop) <= 0) ||
(fctx->step.sign == NUMERIC_NEG &&
cmp_var(&fctx->current, &fctx->stop) >= 0))
{
Numeric result = make_result(&fctx->current);
/* switch to memory context appropriate for iteration calculation */
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* increment current in preparation for next iteration */
add_var(&fctx->current, &fctx->step, &fctx->current);
MemoryContextSwitchTo(oldcontext);
/* do when there is more left to send */
SRF_RETURN_NEXT(funcctx, NumericGetDatum(result));
}
else
/* do when there is no more left */
SRF_RETURN_DONE(funcctx);
}
/*
* Implements the numeric version of the width_bucket() function
* defined by SQL2003. See also width_bucket_float8().
*
* 'bound1' and 'bound2' are the lower and upper bounds of the
* histogram's range, respectively. 'count' is the number of buckets
* in the histogram. width_bucket() returns an integer indicating the
* bucket number that 'operand' belongs to in an equiwidth histogram
* with the specified characteristics. An operand smaller than the
* lower bound is assigned to bucket 0. An operand greater than the
* upper bound is assigned to an additional bucket (with number
* count+1). We don't allow "NaN" for any of the numeric arguments.
*/
Datum
width_bucket_numeric(PG_FUNCTION_ARGS)
{
Numeric operand = PG_GETARG_NUMERIC(0);
Numeric bound1 = PG_GETARG_NUMERIC(1);
Numeric bound2 = PG_GETARG_NUMERIC(2);
int32 count = PG_GETARG_INT32(3);
NumericVar count_var;
NumericVar result_var;
int32 result;
if (count <= 0)
ereport(ERROR,
2005-10-15 04:49:52 +02:00
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION),
errmsg("count must be greater than zero")));
if (NUMERIC_IS_NAN(operand) ||
NUMERIC_IS_NAN(bound1) ||
NUMERIC_IS_NAN(bound2))
ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION),
errmsg("operand, lower bound, and upper bound cannot be NaN")));
init_var(&result_var);
init_var(&count_var);
/* Convert 'count' to a numeric, for ease of use later */
int64_to_numericvar((int64) count, &count_var);
switch (cmp_numerics(bound1, bound2))
{
case 0:
ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION),
errmsg("lower bound cannot equal upper bound")));
break;
2004-08-29 07:07:03 +02:00
/* bound1 < bound2 */
case -1:
if (cmp_numerics(operand, bound1) < 0)
set_var_from_var(&const_zero, &result_var);
else if (cmp_numerics(operand, bound2) >= 0)
add_var(&count_var, &const_one, &result_var);
else
compute_bucket(operand, bound1, bound2,
&count_var, &result_var);
break;
2004-08-29 07:07:03 +02:00
/* bound1 > bound2 */
case 1:
if (cmp_numerics(operand, bound1) > 0)
set_var_from_var(&const_zero, &result_var);
else if (cmp_numerics(operand, bound2) <= 0)
add_var(&count_var, &const_one, &result_var);
else
compute_bucket(operand, bound1, bound2,
&count_var, &result_var);
break;
}
/* if result exceeds the range of a legal int4, we ereport here */
if (!numericvar_to_int32(&result_var, &result))
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
free_var(&count_var);
free_var(&result_var);
PG_RETURN_INT32(result);
}
/*
* If 'operand' is not outside the bucket range, determine the correct
* bucket for it to go. The calculations performed by this function
* are derived directly from the SQL2003 spec.
*/
static void
compute_bucket(Numeric operand, Numeric bound1, Numeric bound2,
const NumericVar *count_var, NumericVar *result_var)
{
2004-08-29 07:07:03 +02:00
NumericVar bound1_var;
NumericVar bound2_var;
NumericVar operand_var;
init_var_from_num(bound1, &bound1_var);
init_var_from_num(bound2, &bound2_var);
init_var_from_num(operand, &operand_var);
if (cmp_var(&bound1_var, &bound2_var) < 0)
{
sub_var(&operand_var, &bound1_var, &operand_var);
sub_var(&bound2_var, &bound1_var, &bound2_var);
div_var(&operand_var, &bound2_var, result_var,
select_div_scale(&operand_var, &bound2_var), true);
}
else
{
sub_var(&bound1_var, &operand_var, &operand_var);
sub_var(&bound1_var, &bound2_var, &bound1_var);
div_var(&operand_var, &bound1_var, result_var,
select_div_scale(&operand_var, &bound1_var), true);
}
mul_var(result_var, count_var, result_var,
result_var->dscale + count_var->dscale);
add_var(result_var, &const_one, result_var);
floor_var(result_var, result_var);
free_var(&bound1_var);
free_var(&bound2_var);
free_var(&operand_var);
2004-08-29 07:07:03 +02:00
}
/* ----------------------------------------------------------------------
*
* Comparison functions
*
* Note: btree indexes need these routines not to leak memory; therefore,
* be careful to free working copies of toasted datums. Most places don't
* need to be so careful.
*
* Sort support:
*
* We implement the sortsupport strategy routine in order to get the benefit of
* abbreviation. The ordinary numeric comparison can be quite slow as a result
* of palloc/pfree cycles (due to detoasting packed values for alignment);
* while this could be worked on itself, the abbreviation strategy gives more
* speedup in many common cases.
*
* Two different representations are used for the abbreviated form, one in
* int32 and one in int64, whichever fits into a by-value Datum. In both cases
* the representation is negated relative to the original value, because we use
* the largest negative value for NaN, which sorts higher than other values. We
* convert the absolute value of the numeric to a 31-bit or 63-bit positive
* value, and then negate it if the original number was positive.
*
* We abort the abbreviation process if the abbreviation cardinality is below
* 0.01% of the row count (1 per 10k non-null rows). The actual break-even
* point is somewhat below that, perhaps 1 per 30k (at 1 per 100k there's a
* very small penalty), but we don't want to build up too many abbreviated
* values before first testing for abort, so we take the slightly pessimistic
* number. We make no attempt to estimate the cardinality of the real values,
* since it plays no part in the cost model here (if the abbreviation is equal,
* the cost of comparing equal and unequal underlying values is comparable).
* We discontinue even checking for abort (saving us the hashing overhead) if
* the estimated cardinality gets to 100k; that would be enough to support many
* billions of rows while doing no worse than breaking even.
*
* ----------------------------------------------------------------------
*/
/*
* Sort support strategy routine.
*/
Datum
numeric_sortsupport(PG_FUNCTION_ARGS)
{
SortSupport ssup = (SortSupport) PG_GETARG_POINTER(0);
ssup->comparator = numeric_fast_cmp;
if (ssup->abbreviate)
{
NumericSortSupport *nss;
MemoryContext oldcontext = MemoryContextSwitchTo(ssup->ssup_cxt);
nss = palloc(sizeof(NumericSortSupport));
/*
* palloc a buffer for handling unaligned packed values in addition to
* the support struct
*/
nss->buf = palloc(VARATT_SHORT_MAX + VARHDRSZ + 1);
nss->input_count = 0;
nss->estimating = true;
initHyperLogLog(&nss->abbr_card, 10);
ssup->ssup_extra = nss;
ssup->abbrev_full_comparator = ssup->comparator;
ssup->comparator = numeric_cmp_abbrev;
ssup->abbrev_converter = numeric_abbrev_convert;
ssup->abbrev_abort = numeric_abbrev_abort;
MemoryContextSwitchTo(oldcontext);
}
PG_RETURN_VOID();
}
/*
* Abbreviate a numeric datum, handling NaNs and detoasting
* (must not leak memory!)
*/
static Datum
numeric_abbrev_convert(Datum original_datum, SortSupport ssup)
{
NumericSortSupport *nss = ssup->ssup_extra;
void *original_varatt = PG_DETOAST_DATUM_PACKED(original_datum);
Numeric value;
Datum result;
nss->input_count += 1;
/*
* This is to handle packed datums without needing a palloc/pfree cycle;
* we keep and reuse a buffer large enough to handle any short datum.
*/
if (VARATT_IS_SHORT(original_varatt))
{
void *buf = nss->buf;
Size sz = VARSIZE_SHORT(original_varatt) - VARHDRSZ_SHORT;
Assert(sz <= VARATT_SHORT_MAX - VARHDRSZ_SHORT);
SET_VARSIZE(buf, VARHDRSZ + sz);
memcpy(VARDATA(buf), VARDATA_SHORT(original_varatt), sz);
value = (Numeric) buf;
}
else
value = (Numeric) original_varatt;
if (NUMERIC_IS_NAN(value))
{
result = NUMERIC_ABBREV_NAN;
}
else
{
NumericVar var;
init_var_from_num(value, &var);
result = numeric_abbrev_convert_var(&var, nss);
}
/* should happen only for external/compressed toasts */
if ((Pointer) original_varatt != DatumGetPointer(original_datum))
pfree(original_varatt);
return result;
}
/*
* Consider whether to abort abbreviation.
*
* We pay no attention to the cardinality of the non-abbreviated data. There is
* no reason to do so: unlike text, we have no fast check for equal values, so
* we pay the full overhead whenever the abbreviations are equal regardless of
* whether the underlying values are also equal.
*/
static bool
numeric_abbrev_abort(int memtupcount, SortSupport ssup)
{
NumericSortSupport *nss = ssup->ssup_extra;
double abbr_card;
if (memtupcount < 10000 || nss->input_count < 10000 || !nss->estimating)
return false;
abbr_card = estimateHyperLogLog(&nss->abbr_card);
/*
* If we have >100k distinct values, then even if we were sorting many
* billion rows we'd likely still break even, and the penalty of undoing
* that many rows of abbrevs would probably not be worth it. Stop even
* counting at that point.
*/
if (abbr_card > 100000.0)
{
#ifdef TRACE_SORT
if (trace_sort)
elog(LOG,
"numeric_abbrev: estimation ends at cardinality %f"
" after " INT64_FORMAT " values (%d rows)",
abbr_card, nss->input_count, memtupcount);
#endif
nss->estimating = false;
return false;
}
/*
* Target minimum cardinality is 1 per ~10k of non-null inputs. (The
* break even point is somewhere between one per 100k rows, where
* abbreviation has a very slight penalty, and 1 per 10k where it wins by
* a measurable percentage.) We use the relatively pessimistic 10k
* threshold, and add a 0.5 row fudge factor, because it allows us to
* abort earlier on genuinely pathological data where we've had exactly
* one abbreviated value in the first 10k (non-null) rows.
*/
if (abbr_card < nss->input_count / 10000.0 + 0.5)
{
#ifdef TRACE_SORT
if (trace_sort)
elog(LOG,
"numeric_abbrev: aborting abbreviation at cardinality %f"
" below threshold %f after " INT64_FORMAT " values (%d rows)",
abbr_card, nss->input_count / 10000.0 + 0.5,
nss->input_count, memtupcount);
#endif
return true;
}
#ifdef TRACE_SORT
if (trace_sort)
elog(LOG,
"numeric_abbrev: cardinality %f"
" after " INT64_FORMAT " values (%d rows)",
abbr_card, nss->input_count, memtupcount);
#endif
return false;
}
/*
* Non-fmgr interface to the comparison routine to allow sortsupport to elide
* the fmgr call. The saving here is small given how slow numeric comparisons
* are, but it is a required part of the sort support API when abbreviations
* are performed.
*
* Two palloc/pfree cycles could be saved here by using persistent buffers for
* aligning short-varlena inputs, but this has not so far been considered to
* be worth the effort.
*/
static int
numeric_fast_cmp(Datum x, Datum y, SortSupport ssup)
{
Numeric nx = DatumGetNumeric(x);
Numeric ny = DatumGetNumeric(y);
int result;
result = cmp_numerics(nx, ny);
if ((Pointer) nx != DatumGetPointer(x))
pfree(nx);
if ((Pointer) ny != DatumGetPointer(y))
pfree(ny);
return result;
}
/*
* Compare abbreviations of values. (Abbreviations may be equal where the true
* values differ, but if the abbreviations differ, they must reflect the
* ordering of the true values.)
*/
static int
numeric_cmp_abbrev(Datum x, Datum y, SortSupport ssup)
{
/*
* NOTE WELL: this is intentionally backwards, because the abbreviation is
* negated relative to the original value, to handle NaN.
*/
if (DatumGetNumericAbbrev(x) < DatumGetNumericAbbrev(y))
return 1;
if (DatumGetNumericAbbrev(x) > DatumGetNumericAbbrev(y))
return -1;
return 0;
}
/*
* Abbreviate a NumericVar according to the available bit size.
*
* The 31-bit value is constructed as:
*
* 0 + 7bits digit weight + 24 bits digit value
*
* where the digit weight is in single decimal digits, not digit words, and
* stored in excess-44 representation[1]. The 24-bit digit value is the 7 most
* significant decimal digits of the value converted to binary. Values whose
* weights would fall outside the representable range are rounded off to zero
* (which is also used to represent actual zeros) or to 0x7FFFFFFF (which
* otherwise cannot occur). Abbreviation therefore fails to gain any advantage
* where values are outside the range 10^-44 to 10^83, which is not considered
* to be a serious limitation, or when values are of the same magnitude and
* equal in the first 7 decimal digits, which is considered to be an
* unavoidable limitation given the available bits. (Stealing three more bits
* to compare another digit would narrow the range of representable weights by
* a factor of 8, which starts to look like a real limiting factor.)
*
* (The value 44 for the excess is essentially arbitrary)
*
* The 63-bit value is constructed as:
*
* 0 + 7bits weight + 4 x 14-bit packed digit words
*
* The weight in this case is again stored in excess-44, but this time it is
* the original weight in digit words (i.e. powers of 10000). The first four
* digit words of the value (if present; trailing zeros are assumed as needed)
* are packed into 14 bits each to form the rest of the value. Again,
* out-of-range values are rounded off to 0 or 0x7FFFFFFFFFFFFFFF. The
* representable range in this case is 10^-176 to 10^332, which is considered
* to be good enough for all practical purposes, and comparison of 4 words
* means that at least 13 decimal digits are compared, which is considered to
* be a reasonable compromise between effectiveness and efficiency in computing
* the abbreviation.
*
* (The value 44 for the excess is even more arbitrary here, it was chosen just
* to match the value used in the 31-bit case)
*
* [1] - Excess-k representation means that the value is offset by adding 'k'
* and then treated as unsigned, so the smallest representable value is stored
* with all bits zero. This allows simple comparisons to work on the composite
* value.
*/
#if NUMERIC_ABBREV_BITS == 64
static Datum
numeric_abbrev_convert_var(const NumericVar *var, NumericSortSupport *nss)
{
int ndigits = var->ndigits;
int weight = var->weight;
int64 result;
if (ndigits == 0 || weight < -44)
{
result = 0;
}
else if (weight > 83)
{
result = PG_INT64_MAX;
}
else
{
result = ((int64) (weight + 44) << 56);
switch (ndigits)
{
default:
result |= ((int64) var->digits[3]);
/* FALLTHROUGH */
case 3:
result |= ((int64) var->digits[2]) << 14;
/* FALLTHROUGH */
case 2:
result |= ((int64) var->digits[1]) << 28;
/* FALLTHROUGH */
case 1:
result |= ((int64) var->digits[0]) << 42;
break;
}
}
/* the abbrev is negated relative to the original */
if (var->sign == NUMERIC_POS)
result = -result;
if (nss->estimating)
{
uint32 tmp = ((uint32) result
^ (uint32) ((uint64) result >> 32));
addHyperLogLog(&nss->abbr_card, DatumGetUInt32(hash_uint32(tmp)));
}
return NumericAbbrevGetDatum(result);
}
Phase 2 of pgindent updates. Change pg_bsd_indent to follow upstream rules for placement of comments to the right of code, and remove pgindent hack that caused comments following #endif to not obey the general rule. Commit e3860ffa4dd0dad0dd9eea4be9cc1412373a8c89 wasn't actually using the published version of pg_bsd_indent, but a hacked-up version that tried to minimize the amount of movement of comments to the right of code. The situation of interest is where such a comment has to be moved to the right of its default placement at column 33 because there's code there. BSD indent has always moved right in units of tab stops in such cases --- but in the previous incarnation, indent was working in 8-space tab stops, while now it knows we use 4-space tabs. So the net result is that in about half the cases, such comments are placed one tab stop left of before. This is better all around: it leaves more room on the line for comment text, and it means that in such cases the comment uniformly starts at the next 4-space tab stop after the code, rather than sometimes one and sometimes two tabs after. Also, ensure that comments following #endif are indented the same as comments following other preprocessor commands such as #else. That inconsistency turns out to have been self-inflicted damage from a poorly-thought-through post-indent "fixup" in pgindent. This patch is much less interesting than the first round of indent changes, but also bulkier, so I thought it best to separate the effects. Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:18:54 +02:00
#endif /* NUMERIC_ABBREV_BITS == 64 */
#if NUMERIC_ABBREV_BITS == 32
static Datum
numeric_abbrev_convert_var(const NumericVar *var, NumericSortSupport *nss)
{
int ndigits = var->ndigits;
int weight = var->weight;
int32 result;
if (ndigits == 0 || weight < -11)
{
result = 0;
}
else if (weight > 20)
{
result = PG_INT32_MAX;
}
else
{
NumericDigit nxt1 = (ndigits > 1) ? var->digits[1] : 0;
weight = (weight + 11) * 4;
result = var->digits[0];
/*
* "result" now has 1 to 4 nonzero decimal digits. We pack in more
* digits to make 7 in total (largest we can fit in 24 bits)
*/
if (result > 999)
{
/* already have 4 digits, add 3 more */
result = (result * 1000) + (nxt1 / 10);
weight += 3;
}
else if (result > 99)
{
/* already have 3 digits, add 4 more */
result = (result * 10000) + nxt1;
weight += 2;
}
else if (result > 9)
{
NumericDigit nxt2 = (ndigits > 2) ? var->digits[2] : 0;
/* already have 2 digits, add 5 more */
result = (result * 100000) + (nxt1 * 10) + (nxt2 / 1000);
weight += 1;
}
else
{
NumericDigit nxt2 = (ndigits > 2) ? var->digits[2] : 0;
/* already have 1 digit, add 6 more */
result = (result * 1000000) + (nxt1 * 100) + (nxt2 / 100);
}
result = result | (weight << 24);
}
/* the abbrev is negated relative to the original */
if (var->sign == NUMERIC_POS)
result = -result;
if (nss->estimating)
{
uint32 tmp = (uint32) result;
addHyperLogLog(&nss->abbr_card, DatumGetUInt32(hash_uint32(tmp)));
}
return NumericAbbrevGetDatum(result);
}
Phase 2 of pgindent updates. Change pg_bsd_indent to follow upstream rules for placement of comments to the right of code, and remove pgindent hack that caused comments following #endif to not obey the general rule. Commit e3860ffa4dd0dad0dd9eea4be9cc1412373a8c89 wasn't actually using the published version of pg_bsd_indent, but a hacked-up version that tried to minimize the amount of movement of comments to the right of code. The situation of interest is where such a comment has to be moved to the right of its default placement at column 33 because there's code there. BSD indent has always moved right in units of tab stops in such cases --- but in the previous incarnation, indent was working in 8-space tab stops, while now it knows we use 4-space tabs. So the net result is that in about half the cases, such comments are placed one tab stop left of before. This is better all around: it leaves more room on the line for comment text, and it means that in such cases the comment uniformly starts at the next 4-space tab stop after the code, rather than sometimes one and sometimes two tabs after. Also, ensure that comments following #endif are indented the same as comments following other preprocessor commands such as #else. That inconsistency turns out to have been self-inflicted damage from a poorly-thought-through post-indent "fixup" in pgindent. This patch is much less interesting than the first round of indent changes, but also bulkier, so I thought it best to separate the effects. Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:18:54 +02:00
#endif /* NUMERIC_ABBREV_BITS == 32 */
/*
* Ordinary (non-sortsupport) comparisons follow.
*/
Datum
numeric_cmp(PG_FUNCTION_ARGS)
{
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
int result;
result = cmp_numerics(num1, num2);
PG_FREE_IF_COPY(num1, 0);
PG_FREE_IF_COPY(num2, 1);
PG_RETURN_INT32(result);
}
Datum
numeric_eq(PG_FUNCTION_ARGS)
{
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
bool result;
result = cmp_numerics(num1, num2) == 0;
PG_FREE_IF_COPY(num1, 0);
PG_FREE_IF_COPY(num2, 1);
PG_RETURN_BOOL(result);
}
Datum
numeric_ne(PG_FUNCTION_ARGS)
{
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
bool result;
result = cmp_numerics(num1, num2) != 0;
PG_FREE_IF_COPY(num1, 0);
PG_FREE_IF_COPY(num2, 1);
PG_RETURN_BOOL(result);
}
Datum
numeric_gt(PG_FUNCTION_ARGS)
{
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
bool result;
result = cmp_numerics(num1, num2) > 0;
PG_FREE_IF_COPY(num1, 0);
PG_FREE_IF_COPY(num2, 1);
PG_RETURN_BOOL(result);
}
Datum
numeric_ge(PG_FUNCTION_ARGS)
{
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
bool result;
result = cmp_numerics(num1, num2) >= 0;
PG_FREE_IF_COPY(num1, 0);
PG_FREE_IF_COPY(num2, 1);
PG_RETURN_BOOL(result);
}
Datum
numeric_lt(PG_FUNCTION_ARGS)
{
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
bool result;
result = cmp_numerics(num1, num2) < 0;
PG_FREE_IF_COPY(num1, 0);
PG_FREE_IF_COPY(num2, 1);
PG_RETURN_BOOL(result);
}
Datum
numeric_le(PG_FUNCTION_ARGS)
{
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
bool result;
result = cmp_numerics(num1, num2) <= 0;
PG_FREE_IF_COPY(num1, 0);
PG_FREE_IF_COPY(num2, 1);
PG_RETURN_BOOL(result);
}
static int
cmp_numerics(Numeric num1, Numeric num2)
{
int result;
/*
2005-10-15 04:49:52 +02:00
* We consider all NANs to be equal and larger than any non-NAN. This is
* somewhat arbitrary; the important thing is to have a consistent sort
* order.
*/
if (NUMERIC_IS_NAN(num1))
{
if (NUMERIC_IS_NAN(num2))
result = 0; /* NAN = NAN */
else
result = 1; /* NAN > non-NAN */
}
else if (NUMERIC_IS_NAN(num2))
{
result = -1; /* non-NAN < NAN */
}
else
{
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
result = cmp_var_common(NUMERIC_DIGITS(num1), NUMERIC_NDIGITS(num1),
NUMERIC_WEIGHT(num1), NUMERIC_SIGN(num1),
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
NUMERIC_DIGITS(num2), NUMERIC_NDIGITS(num2),
NUMERIC_WEIGHT(num2), NUMERIC_SIGN(num2));
}
return result;
}
/*
* in_range support function for numeric.
*/
Datum
in_range_numeric_numeric(PG_FUNCTION_ARGS)
{
Numeric val = PG_GETARG_NUMERIC(0);
Numeric base = PG_GETARG_NUMERIC(1);
Numeric offset = PG_GETARG_NUMERIC(2);
bool sub = PG_GETARG_BOOL(3);
bool less = PG_GETARG_BOOL(4);
bool result;
/*
* Reject negative or NaN offset. Negative is per spec, and NaN is
* because appropriate semantics for that seem non-obvious.
*/
if (NUMERIC_IS_NAN(offset) || NUMERIC_SIGN(offset) == NUMERIC_NEG)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
/*
* Deal with cases where val and/or base is NaN, following the rule that
* NaN sorts after non-NaN (cf cmp_numerics). The offset cannot affect
* the conclusion.
*/
if (NUMERIC_IS_NAN(val))
{
if (NUMERIC_IS_NAN(base))
result = true; /* NAN = NAN */
else
result = !less; /* NAN > non-NAN */
}
else if (NUMERIC_IS_NAN(base))
{
result = less; /* non-NAN < NAN */
}
else
{
/*
* Otherwise go ahead and compute base +/- offset. While it's
* possible for this to overflow the numeric format, it's unlikely
* enough that we don't take measures to prevent it.
*/
NumericVar valv;
NumericVar basev;
NumericVar offsetv;
NumericVar sum;
init_var_from_num(val, &valv);
init_var_from_num(base, &basev);
init_var_from_num(offset, &offsetv);
init_var(&sum);
if (sub)
sub_var(&basev, &offsetv, &sum);
else
add_var(&basev, &offsetv, &sum);
if (less)
result = (cmp_var(&valv, &sum) <= 0);
else
result = (cmp_var(&valv, &sum) >= 0);
free_var(&sum);
}
PG_FREE_IF_COPY(val, 0);
PG_FREE_IF_COPY(base, 1);
PG_FREE_IF_COPY(offset, 2);
PG_RETURN_BOOL(result);
}
Datum
hash_numeric(PG_FUNCTION_ARGS)
{
2007-11-15 22:14:46 +01:00
Numeric key = PG_GETARG_NUMERIC(0);
Datum digit_hash;
Datum result;
int weight;
int start_offset;
int end_offset;
int i;
int hash_len;
2011-04-10 17:42:00 +02:00
NumericDigit *digits;
/* If it's NaN, don't try to hash the rest of the fields */
if (NUMERIC_IS_NAN(key))
PG_RETURN_UINT32(0);
weight = NUMERIC_WEIGHT(key);
start_offset = 0;
2007-11-15 22:14:46 +01:00
end_offset = 0;
/*
2007-11-15 22:14:46 +01:00
* Omit any leading or trailing zeros from the input to the hash. The
* numeric implementation *should* guarantee that leading and trailing
* zeros are suppressed, but we're paranoid. Note that we measure the
* starting and ending offsets in units of NumericDigits, not bytes.
*/
digits = NUMERIC_DIGITS(key);
for (i = 0; i < NUMERIC_NDIGITS(key); i++)
{
if (digits[i] != (NumericDigit) 0)
break;
start_offset++;
2007-11-15 22:14:46 +01:00
/*
2007-11-15 22:14:46 +01:00
* The weight is effectively the # of digits before the decimal point,
* so decrement it for each leading zero we skip.
*/
weight--;
}
/*
2007-11-15 22:14:46 +01:00
* If there are no non-zero digits, then the value of the number is zero,
* regardless of any other fields.
*/
if (NUMERIC_NDIGITS(key) == start_offset)
PG_RETURN_UINT32(-1);
for (i = NUMERIC_NDIGITS(key) - 1; i >= 0; i--)
{
if (digits[i] != (NumericDigit) 0)
break;
end_offset++;
}
/* If we get here, there should be at least one non-zero digit */
Assert(start_offset + end_offset < NUMERIC_NDIGITS(key));
/*
2007-11-15 22:14:46 +01:00
* Note that we don't hash on the Numeric's scale, since two numerics can
* compare equal but have different scales. We also don't hash on the
* sign, although we could: since a sign difference implies inequality,
* this shouldn't affect correctness.
*/
hash_len = NUMERIC_NDIGITS(key) - start_offset - end_offset;
digit_hash = hash_any((unsigned char *) (NUMERIC_DIGITS(key) + start_offset),
2007-11-15 22:14:46 +01:00
hash_len * sizeof(NumericDigit));
/* Mix in the weight, via XOR */
result = digit_hash ^ weight;
PG_RETURN_DATUM(result);
}
/*
* Returns 64-bit value by hashing a value to a 64-bit value, with a seed.
* Otherwise, similar to hash_numeric.
*/
Datum
hash_numeric_extended(PG_FUNCTION_ARGS)
{
Numeric key = PG_GETARG_NUMERIC(0);
uint64 seed = PG_GETARG_INT64(1);
Datum digit_hash;
Datum result;
int weight;
int start_offset;
int end_offset;
int i;
int hash_len;
NumericDigit *digits;
if (NUMERIC_IS_NAN(key))
PG_RETURN_UINT64(seed);
weight = NUMERIC_WEIGHT(key);
start_offset = 0;
end_offset = 0;
digits = NUMERIC_DIGITS(key);
for (i = 0; i < NUMERIC_NDIGITS(key); i++)
{
if (digits[i] != (NumericDigit) 0)
break;
start_offset++;
weight--;
}
if (NUMERIC_NDIGITS(key) == start_offset)
PG_RETURN_UINT64(seed - 1);
for (i = NUMERIC_NDIGITS(key) - 1; i >= 0; i--)
{
if (digits[i] != (NumericDigit) 0)
break;
end_offset++;
}
Assert(start_offset + end_offset < NUMERIC_NDIGITS(key));
hash_len = NUMERIC_NDIGITS(key) - start_offset - end_offset;
digit_hash = hash_any_extended((unsigned char *) (NUMERIC_DIGITS(key)
+ start_offset),
hash_len * sizeof(NumericDigit),
seed);
result = UInt64GetDatum(DatumGetUInt64(digit_hash) ^ weight);
PG_RETURN_DATUM(result);
}
/* ----------------------------------------------------------------------
*
* Basic arithmetic functions
*
* ----------------------------------------------------------------------
*/
/*
* numeric_add() -
*
* Add two numerics
*/
Datum
numeric_add(PG_FUNCTION_ARGS)
{
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
Numeric res;
res = numeric_add_opt_error(num1, num2, NULL);
PG_RETURN_NUMERIC(res);
}
/*
* numeric_add_opt_error() -
*
* Internal version of numeric_add(). If "*have_error" flag is provided,
* on error it's set to true, NULL returned. This is helpful when caller
* need to handle errors by itself.
*/
Numeric
numeric_add_opt_error(Numeric num1, Numeric num2, bool *have_error)
{
NumericVar arg1;
NumericVar arg2;
NumericVar result;
Numeric res;
/*
* Handle NaN
*/
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
return make_result(&const_nan);
/*
* Unpack the values, let add_var() compute the result and return it.
*/
init_var_from_num(num1, &arg1);
init_var_from_num(num2, &arg2);
init_var(&result);
add_var(&arg1, &arg2, &result);
res = make_result_opt_error(&result, have_error);
free_var(&result);
return res;
}
/*
* numeric_sub() -
*
* Subtract one numeric from another
*/
Datum
numeric_sub(PG_FUNCTION_ARGS)
{
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
Numeric res;
res = numeric_sub_opt_error(num1, num2, NULL);
PG_RETURN_NUMERIC(res);
}
/*
* numeric_sub_opt_error() -
*
* Internal version of numeric_sub(). If "*have_error" flag is provided,
* on error it's set to true, NULL returned. This is helpful when caller
* need to handle errors by itself.
*/
Numeric
numeric_sub_opt_error(Numeric num1, Numeric num2, bool *have_error)
{
NumericVar arg1;
NumericVar arg2;
NumericVar result;
Numeric res;
/*
* Handle NaN
*/
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
return make_result(&const_nan);
/*
* Unpack the values, let sub_var() compute the result and return it.
*/
init_var_from_num(num1, &arg1);
init_var_from_num(num2, &arg2);
init_var(&result);
sub_var(&arg1, &arg2, &result);
res = make_result_opt_error(&result, have_error);
free_var(&result);
return res;
}
/*
* numeric_mul() -
*
* Calculate the product of two numerics
*/
Datum
numeric_mul(PG_FUNCTION_ARGS)
{
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
Numeric res;
res = numeric_mul_opt_error(num1, num2, NULL);
PG_RETURN_NUMERIC(res);
}
/*
* numeric_mul_opt_error() -
*
* Internal version of numeric_mul(). If "*have_error" flag is provided,
* on error it's set to true, NULL returned. This is helpful when caller
* need to handle errors by itself.
*/
Numeric
numeric_mul_opt_error(Numeric num1, Numeric num2, bool *have_error)
{
NumericVar arg1;
NumericVar arg2;
NumericVar result;
Numeric res;
/*
* Handle NaN
*/
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
return make_result(&const_nan);
/*
* Unpack the values, let mul_var() compute the result and return it.
2005-10-15 04:49:52 +02:00
* Unlike add_var() and sub_var(), mul_var() will round its result. In the
* case of numeric_mul(), which is invoked for the * operator on numerics,
* we request exact representation for the product (rscale = sum(dscale of
* arg1, dscale of arg2)).
*/
init_var_from_num(num1, &arg1);
init_var_from_num(num2, &arg2);
init_var(&result);
mul_var(&arg1, &arg2, &result, arg1.dscale + arg2.dscale);
res = make_result_opt_error(&result, have_error);
free_var(&result);
return res;
}
/*
* numeric_div() -
*
* Divide one numeric into another
*/
Datum
numeric_div(PG_FUNCTION_ARGS)
{
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
Numeric res;
res = numeric_div_opt_error(num1, num2, NULL);
PG_RETURN_NUMERIC(res);
}
/*
* numeric_div_opt_error() -
*
* Internal version of numeric_div(). If "*have_error" flag is provided,
* on error it's set to true, NULL returned. This is helpful when caller
* need to handle errors by itself.
*/
Numeric
numeric_div_opt_error(Numeric num1, Numeric num2, bool *have_error)
{
NumericVar arg1;
NumericVar arg2;
NumericVar result;
Numeric res;
int rscale;
if (have_error)
*have_error = false;
/*
* Handle NaN
*/
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
return make_result(&const_nan);
/*
* Unpack the arguments
*/
init_var_from_num(num1, &arg1);
init_var_from_num(num2, &arg2);
init_var(&result);
/*
* Select scale for division result
*/
rscale = select_div_scale(&arg1, &arg2);
/*
* If "have_error" is provided, check for division by zero here
*/
if (have_error && (arg2.ndigits == 0 || arg2.digits[0] == 0))
{
*have_error = true;
return NULL;
}
/*
* Do the divide and return the result
*/
div_var(&arg1, &arg2, &result, rscale, true);
res = make_result_opt_error(&result, have_error);
free_var(&result);
return res;
}
/*
* numeric_div_trunc() -
*
* Divide one numeric into another, truncating the result to an integer
*/
Datum
numeric_div_trunc(PG_FUNCTION_ARGS)
{
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
NumericVar arg1;
NumericVar arg2;
NumericVar result;
Numeric res;
/*
* Handle NaN
*/
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
PG_RETURN_NUMERIC(make_result(&const_nan));
/*
* Unpack the arguments
*/
init_var_from_num(num1, &arg1);
init_var_from_num(num2, &arg2);
init_var(&result);
/*
* Do the divide and return the result
*/
div_var(&arg1, &arg2, &result, 0, false);
res = make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
}
/*
* numeric_mod() -
*
* Calculate the modulo of two numerics
*/
Datum
numeric_mod(PG_FUNCTION_ARGS)
{
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
Numeric res;
res = numeric_mod_opt_error(num1, num2, NULL);
PG_RETURN_NUMERIC(res);
}
/*
* numeric_mod_opt_error() -
*
* Internal version of numeric_mod(). If "*have_error" flag is provided,
* on error it's set to true, NULL returned. This is helpful when caller
* need to handle errors by itself.
*/
Numeric
numeric_mod_opt_error(Numeric num1, Numeric num2, bool *have_error)
{
Numeric res;
NumericVar arg1;
NumericVar arg2;
NumericVar result;
if (have_error)
*have_error = false;
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
return make_result(&const_nan);
init_var_from_num(num1, &arg1);
init_var_from_num(num2, &arg2);
init_var(&result);
/*
* If "have_error" is provided, check for division by zero here
*/
if (have_error && (arg2.ndigits == 0 || arg2.digits[0] == 0))
{
*have_error = true;
return NULL;
}
mod_var(&arg1, &arg2, &result);
res = make_result_opt_error(&result, NULL);
free_var(&result);
return res;
}
/*
* numeric_inc() -
*
* Increment a number by one
*/
Datum
numeric_inc(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
NumericVar arg;
Numeric res;
/*
* Handle NaN
*/
if (NUMERIC_IS_NAN(num))
PG_RETURN_NUMERIC(make_result(&const_nan));
/*
* Compute the result and return it
*/
init_var_from_num(num, &arg);
add_var(&arg, &const_one, &arg);
res = make_result(&arg);
free_var(&arg);
PG_RETURN_NUMERIC(res);
}
/*
* numeric_smaller() -
*
* Return the smaller of two numbers
*/
Datum
numeric_smaller(PG_FUNCTION_ARGS)
{
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
/*
2005-10-15 04:49:52 +02:00
* Use cmp_numerics so that this will agree with the comparison operators,
* particularly as regards comparisons involving NaN.
*/
if (cmp_numerics(num1, num2) < 0)
PG_RETURN_NUMERIC(num1);
else
PG_RETURN_NUMERIC(num2);
}
/*
* numeric_larger() -
*
* Return the larger of two numbers
*/
Datum
numeric_larger(PG_FUNCTION_ARGS)
{
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
/*
2005-10-15 04:49:52 +02:00
* Use cmp_numerics so that this will agree with the comparison operators,
* particularly as regards comparisons involving NaN.
*/
if (cmp_numerics(num1, num2) > 0)
PG_RETURN_NUMERIC(num1);
else
PG_RETURN_NUMERIC(num2);
}
/* ----------------------------------------------------------------------
*
* Advanced math functions
*
* ----------------------------------------------------------------------
*/
/*
* numeric_gcd() -
*
* Calculate the greatest common divisor of two numerics
*/
Datum
numeric_gcd(PG_FUNCTION_ARGS)
{
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
NumericVar arg1;
NumericVar arg2;
NumericVar result;
Numeric res;
/*
* Handle NaN
*/
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
PG_RETURN_NUMERIC(make_result(&const_nan));
/*
* Unpack the arguments
*/
init_var_from_num(num1, &arg1);
init_var_from_num(num2, &arg2);
init_var(&result);
/*
* Find the GCD and return the result
*/
gcd_var(&arg1, &arg2, &result);
res = make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
}
/*
* numeric_lcm() -
*
* Calculate the least common multiple of two numerics
*/
Datum
numeric_lcm(PG_FUNCTION_ARGS)
{
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
NumericVar arg1;
NumericVar arg2;
NumericVar result;
Numeric res;
/*
* Handle NaN
*/
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
PG_RETURN_NUMERIC(make_result(&const_nan));
/*
* Unpack the arguments
*/
init_var_from_num(num1, &arg1);
init_var_from_num(num2, &arg2);
init_var(&result);
/*
* Compute the result using lcm(x, y) = abs(x / gcd(x, y) * y), returning
* zero if either input is zero.
*
* Note that the division is guaranteed to be exact, returning an integer
* result, so the LCM is an integral multiple of both x and y. A display
* scale of Min(x.dscale, y.dscale) would be sufficient to represent it,
* but as with other numeric functions, we choose to return a result whose
* display scale is no smaller than either input.
*/
if (arg1.ndigits == 0 || arg2.ndigits == 0)
set_var_from_var(&const_zero, &result);
else
{
gcd_var(&arg1, &arg2, &result);
div_var(&arg1, &result, &result, 0, false);
mul_var(&arg2, &result, &result, arg2.dscale);
result.sign = NUMERIC_POS;
}
result.dscale = Max(arg1.dscale, arg2.dscale);
res = make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
}
/*
* numeric_fac()
*
* Compute factorial
*/
Datum
numeric_fac(PG_FUNCTION_ARGS)
{
int64 num = PG_GETARG_INT64(0);
Numeric res;
NumericVar fact;
NumericVar result;
if (num <= 1)
{
res = make_result(&const_one);
PG_RETURN_NUMERIC(res);
}
/* Fail immediately if the result would overflow */
if (num > 32177)
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value overflows numeric format")));
init_var(&fact);
init_var(&result);
int64_to_numericvar(num, &result);
for (num = num - 1; num > 1; num--)
{
/* this loop can take awhile, so allow it to be interrupted */
CHECK_FOR_INTERRUPTS();
int64_to_numericvar(num, &fact);
mul_var(&result, &fact, &result, 0);
}
res = make_result(&result);
free_var(&fact);
free_var(&result);
PG_RETURN_NUMERIC(res);
}
/*
* numeric_sqrt() -
*
* Compute the square root of a numeric.
*/
Datum
numeric_sqrt(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
1999-05-25 18:15:34 +02:00
Numeric res;
NumericVar arg;
NumericVar result;
int sweight;
int rscale;
/*
* Handle NaN
*/
if (NUMERIC_IS_NAN(num))
PG_RETURN_NUMERIC(make_result(&const_nan));
/*
* Unpack the argument and determine the result scale. We choose a scale
2005-10-15 04:49:52 +02:00
* to give at least NUMERIC_MIN_SIG_DIGITS significant digits; but in any
* case not less than the input's dscale.
*/
init_var_from_num(num, &arg);
init_var(&result);
/* Assume the input was normalized, so arg.weight is accurate */
sweight = (arg.weight + 1) * DEC_DIGITS / 2 - 1;
rscale = NUMERIC_MIN_SIG_DIGITS - sweight;
rscale = Max(rscale, arg.dscale);
rscale = Max(rscale, NUMERIC_MIN_DISPLAY_SCALE);
rscale = Min(rscale, NUMERIC_MAX_DISPLAY_SCALE);
/*
* Let sqrt_var() do the calculation and return the result.
*/
sqrt_var(&arg, &result, rscale);
res = make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
}
/*
* numeric_exp() -
*
* Raise e to the power of x
*/
Datum
numeric_exp(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
1999-05-25 18:15:34 +02:00
Numeric res;
NumericVar arg;
NumericVar result;
int rscale;
double val;
/*
* Handle NaN
*/
if (NUMERIC_IS_NAN(num))
PG_RETURN_NUMERIC(make_result(&const_nan));
/*
* Unpack the argument and determine the result scale. We choose a scale
2005-10-15 04:49:52 +02:00
* to give at least NUMERIC_MIN_SIG_DIGITS significant digits; but in any
* case not less than the input's dscale.
*/
init_var_from_num(num, &arg);
init_var(&result);
/* convert input to float8, ignoring overflow */
val = numericvar_to_double_no_overflow(&arg);
/*
2005-10-15 04:49:52 +02:00
* log10(result) = num * log10(e), so this is approximately the decimal
* weight of the result:
*/
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);
rscale = NUMERIC_MIN_SIG_DIGITS - (int) val;
rscale = Max(rscale, arg.dscale);
rscale = Max(rscale, NUMERIC_MIN_DISPLAY_SCALE);
rscale = Min(rscale, NUMERIC_MAX_DISPLAY_SCALE);
/*
* Let exp_var() do the calculation and return the result.
*/
exp_var(&arg, &result, rscale);
res = make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
}
/*
* numeric_ln() -
*
* Compute the natural logarithm of x
*/
Datum
numeric_ln(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
1999-05-25 18:15:34 +02:00
Numeric res;
NumericVar arg;
NumericVar result;
2015-11-14 20:55:38 +01:00
int ln_dweight;
int rscale;
/*
* Handle NaN
*/
if (NUMERIC_IS_NAN(num))
PG_RETURN_NUMERIC(make_result(&const_nan));
init_var_from_num(num, &arg);
init_var(&result);
2015-11-14 20:55:38 +01:00
/* Estimated dweight of logarithm */
ln_dweight = estimate_ln_dweight(&arg);
2015-11-14 20:55:38 +01:00
rscale = NUMERIC_MIN_SIG_DIGITS - ln_dweight;
rscale = Max(rscale, arg.dscale);
rscale = Max(rscale, NUMERIC_MIN_DISPLAY_SCALE);
rscale = Min(rscale, NUMERIC_MAX_DISPLAY_SCALE);
ln_var(&arg, &result, rscale);
res = make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
}
/*
* numeric_log() -
*
* Compute the logarithm of x in a given base
*/
Datum
numeric_log(PG_FUNCTION_ARGS)
{
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
1999-05-25 18:15:34 +02:00
Numeric res;
NumericVar arg1;
NumericVar arg2;
NumericVar result;
/*
* Handle NaN
*/
if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
PG_RETURN_NUMERIC(make_result(&const_nan));
/*
* Initialize things
*/
init_var_from_num(num1, &arg1);
init_var_from_num(num2, &arg2);
init_var(&result);
/*
2005-10-15 04:49:52 +02:00
* Call log_var() to compute and return the result; note it handles scale
* selection itself.
*/
log_var(&arg1, &arg2, &result);
res = make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
}
/*
* numeric_power() -
*
* Raise b to the power of x
*/
Datum
numeric_power(PG_FUNCTION_ARGS)
{
Numeric num1 = PG_GETARG_NUMERIC(0);
Numeric num2 = PG_GETARG_NUMERIC(1);
1999-05-25 18:15:34 +02:00
Numeric res;
NumericVar arg1;
NumericVar arg2;
NumericVar arg2_trunc;
1999-05-25 18:15:34 +02:00
NumericVar result;
/*
* Handle NaN cases. We follow the POSIX spec for pow(3), which says that
* NaN ^ 0 = 1, and 1 ^ NaN = 1, while all other cases with NaN inputs
* yield NaN (with no error).
*/
if (NUMERIC_IS_NAN(num1))
{
if (!NUMERIC_IS_NAN(num2))
{
init_var_from_num(num2, &arg2);
if (cmp_var(&arg2, &const_zero) == 0)
PG_RETURN_NUMERIC(make_result(&const_one));
}
PG_RETURN_NUMERIC(make_result(&const_nan));
}
if (NUMERIC_IS_NAN(num2))
{
init_var_from_num(num1, &arg1);
if (cmp_var(&arg1, &const_one) == 0)
PG_RETURN_NUMERIC(make_result(&const_one));
PG_RETURN_NUMERIC(make_result(&const_nan));
}
/*
* Initialize things
*/
init_var(&arg2_trunc);
init_var(&result);
init_var_from_num(num1, &arg1);
init_var_from_num(num2, &arg2);
set_var_from_var(&arg2, &arg2_trunc);
trunc_var(&arg2_trunc, 0);
/*
* The SQL spec requires that we emit a particular SQLSTATE error code for
* certain error conditions. Specifically, we don't return a
* divide-by-zero error code for 0 ^ -1.
*/
if (cmp_var(&arg1, &const_zero) == 0 &&
cmp_var(&arg2, &const_zero) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
errmsg("zero raised to a negative power is undefined")));
if (cmp_var(&arg1, &const_zero) < 0 &&
cmp_var(&arg2, &arg2_trunc) != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
errmsg("a negative number raised to a non-integer power yields a complex result")));
/*
* Call power_var() to compute and return the result; note it handles
* scale selection itself.
*/
power_var(&arg1, &arg2, &result);
res = make_result(&result);
free_var(&result);
free_var(&arg2_trunc);
PG_RETURN_NUMERIC(res);
}
/*
* numeric_scale() -
*
* Returns the scale, i.e. the count of decimal digits in the fractional part
*/
Datum
numeric_scale(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
if (NUMERIC_IS_NAN(num))
PG_RETURN_NULL();
PG_RETURN_INT32(NUMERIC_DSCALE(num));
}
/*
* Calculate minimum scale for value.
*/
static int
get_min_scale(NumericVar *var)
{
int min_scale;
int last_digit_pos;
/*
* Ordinarily, the input value will be "stripped" so that the last
* NumericDigit is nonzero. But we don't want to get into an infinite
* loop if it isn't, so explicitly find the last nonzero digit.
*/
last_digit_pos = var->ndigits - 1;
while (last_digit_pos >= 0 &&
var->digits[last_digit_pos] == 0)
last_digit_pos--;
if (last_digit_pos >= 0)
{
/* compute min_scale assuming that last ndigit has no zeroes */
min_scale = (last_digit_pos - var->weight) * DEC_DIGITS;
/*
* We could get a negative result if there are no digits after the
* decimal point. In this case the min_scale must be zero.
*/
if (min_scale > 0)
{
/*
* Reduce min_scale if trailing digit(s) in last NumericDigit are
* zero.
*/
NumericDigit last_digit = var->digits[last_digit_pos];
while (last_digit % 10 == 0)
{
min_scale--;
last_digit /= 10;
}
}
else
min_scale = 0;
}
else
min_scale = 0; /* result if input is zero */
return min_scale;
}
/*
* Returns minimum scale required to represent supplied value without loss.
*/
Datum
numeric_min_scale(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
NumericVar arg;
int min_scale;
if (NUMERIC_IS_NAN(num))
PG_RETURN_NULL();
init_var_from_num(num, &arg);
min_scale = get_min_scale(&arg);
free_var(&arg);
PG_RETURN_INT32(min_scale);
}
/*
* Reduce scale of numeric value to represent supplied value without loss.
*/
Datum
numeric_trim_scale(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
Numeric res;
NumericVar result;
if (NUMERIC_IS_NAN(num))
PG_RETURN_NUMERIC(make_result(&const_nan));
init_var_from_num(num, &result);
result.dscale = get_min_scale(&result);
res = make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
}
/* ----------------------------------------------------------------------
*
* Type conversion functions
*
* ----------------------------------------------------------------------
*/
Datum
int4_numeric(PG_FUNCTION_ARGS)
{
int32 val = PG_GETARG_INT32(0);
Numeric res;
NumericVar result;
init_var(&result);
int64_to_numericvar((int64) val, &result);
res = make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
}
int32
numeric_int4_opt_error(Numeric num, bool *have_error)
{
NumericVar x;
int32 result;
if (have_error)
*have_error = false;
/* XXX would it be better to return NULL? */
if (NUMERIC_IS_NAN(num))
{
if (have_error)
{
*have_error = true;
return 0;
}
else
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert NaN to integer")));
}
}
/* Convert to variable format, then convert to int4 */
init_var_from_num(num, &x);
if (!numericvar_to_int32(&x, &result))
{
if (have_error)
{
*have_error = true;
return 0;
}
else
{
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("integer out of range")));
}
}
return result;
}
Datum
numeric_int4(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
PG_RETURN_INT32(numeric_int4_opt_error(num, NULL));
}
/*
* Given a NumericVar, convert it to an int32. If the NumericVar
* exceeds the range of an int32, false is returned, otherwise true is returned.
* The input NumericVar is *not* free'd.
*/
static bool
numericvar_to_int32(const NumericVar *var, int32 *result)
{
2004-08-29 07:07:03 +02:00
int64 val;
if (!numericvar_to_int64(var, &val))
return false;
/* Down-convert to int4 */
*result = (int32) val;
/* Test for overflow by reverse-conversion. */
return ((int64) *result == val);
}
Datum
int8_numeric(PG_FUNCTION_ARGS)
{
int64 val = PG_GETARG_INT64(0);
Numeric res;
NumericVar result;
init_var(&result);
int64_to_numericvar(val, &result);
res = make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
}
Datum
numeric_int8(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
NumericVar x;
int64 result;
/* XXX would it be better to return NULL? */
if (NUMERIC_IS_NAN(num))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert NaN to bigint")));
/* Convert to variable format and thence to int8 */
init_var_from_num(num, &x);
if (!numericvar_to_int64(&x, &result))
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("bigint out of range")));
PG_RETURN_INT64(result);
}
Datum
int2_numeric(PG_FUNCTION_ARGS)
{
int16 val = PG_GETARG_INT16(0);
Numeric res;
NumericVar result;
init_var(&result);
int64_to_numericvar((int64) val, &result);
res = make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
}
Datum
numeric_int2(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
NumericVar x;
int64 val;
int16 result;
/* XXX would it be better to return NULL? */
if (NUMERIC_IS_NAN(num))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert NaN to smallint")));
/* Convert to variable format and thence to int8 */
init_var_from_num(num, &x);
if (!numericvar_to_int64(&x, &val))
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
/* Down-convert to int2 */
result = (int16) val;
/* Test for overflow by reverse-conversion. */
if ((int64) result != val)
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("smallint out of range")));
PG_RETURN_INT16(result);
}
Datum
float8_numeric(PG_FUNCTION_ARGS)
{
float8 val = PG_GETARG_FLOAT8(0);
Numeric res;
NumericVar result;
char buf[DBL_DIG + 100];
if (isnan(val))
PG_RETURN_NUMERIC(make_result(&const_nan));
if (isinf(val))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert infinity to numeric")));
snprintf(buf, sizeof(buf), "%.*g", DBL_DIG, val);
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
(void) set_var_from_str(buf, buf, &result);
res = make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
}
Datum
numeric_float8(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
1999-05-25 18:15:34 +02:00
char *tmp;
Datum result;
if (NUMERIC_IS_NAN(num))
PG_RETURN_FLOAT8(get_float8_nan());
tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
NumericGetDatum(num)));
result = DirectFunctionCall1(float8in, CStringGetDatum(tmp));
pfree(tmp);
PG_RETURN_DATUM(result);
}
/*
* Convert numeric to float8; if out of range, return +/- HUGE_VAL
*
* (internal helper function, not directly callable from SQL)
*/
Datum
numeric_float8_no_overflow(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
double val;
if (NUMERIC_IS_NAN(num))
PG_RETURN_FLOAT8(get_float8_nan());
val = numeric_to_double_no_overflow(num);
PG_RETURN_FLOAT8(val);
}
Datum
float4_numeric(PG_FUNCTION_ARGS)
{
float4 val = PG_GETARG_FLOAT4(0);
Numeric res;
NumericVar result;
char buf[FLT_DIG + 100];
if (isnan(val))
PG_RETURN_NUMERIC(make_result(&const_nan));
if (isinf(val))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot convert infinity to numeric")));
snprintf(buf, sizeof(buf), "%.*g", FLT_DIG, val);
init_var(&result);
/* Assume we need not worry about leading/trailing spaces */
(void) set_var_from_str(buf, buf, &result);
res = make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
}
Datum
numeric_float4(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
1999-05-25 18:15:34 +02:00
char *tmp;
Datum result;
if (NUMERIC_IS_NAN(num))
PG_RETURN_FLOAT4(get_float4_nan());
tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
NumericGetDatum(num)));
result = DirectFunctionCall1(float4in, CStringGetDatum(tmp));
pfree(tmp);
PG_RETURN_DATUM(result);
}
/* ----------------------------------------------------------------------
*
* Aggregate functions
*
* The transition datatype for all these aggregates is declared as INTERNAL.
* Actually, it's a pointer to a NumericAggState allocated in the aggregate
* context. The digit buffers for the NumericVars will be there too.
*
* On platforms which support 128-bit integers some aggregates instead use a
* 128-bit integer based transition datatype to speed up calculations.
*
* ----------------------------------------------------------------------
*/
typedef struct NumericAggState
{
bool calcSumX2; /* if true, calculate sumX2 */
MemoryContext agg_context; /* context we're calculating in */
int64 N; /* count of processed numbers */
NumericSumAccum sumX; /* sum of processed numbers */
NumericSumAccum sumX2; /* sum of squares of processed numbers */
int maxScale; /* maximum scale seen so far */
int64 maxScaleCount; /* number of values seen with maximum scale */
int64 NaNcount; /* count of NaN values (not included in N!) */
} NumericAggState;
/*
* Prepare state data for a numeric aggregate function that needs to compute
* sum, count and optionally sum of squares of the input.
*/
static NumericAggState *
makeNumericAggState(FunctionCallInfo fcinfo, bool calcSumX2)
{
NumericAggState *state;
MemoryContext agg_context;
MemoryContext old_context;
if (!AggCheckCallContext(fcinfo, &agg_context))
elog(ERROR, "aggregate function called in non-aggregate context");
old_context = MemoryContextSwitchTo(agg_context);
state = (NumericAggState *) palloc0(sizeof(NumericAggState));
state->calcSumX2 = calcSumX2;
state->agg_context = agg_context;
MemoryContextSwitchTo(old_context);
return state;
}
/*
* Like makeNumericAggState(), but allocate the state in the current memory
* context.
*/
static NumericAggState *
makeNumericAggStateCurrentContext(bool calcSumX2)
{
NumericAggState *state;
state = (NumericAggState *) palloc0(sizeof(NumericAggState));
state->calcSumX2 = calcSumX2;
state->agg_context = CurrentMemoryContext;
return state;
}
/*
* Accumulate a new input value for numeric aggregate functions.
*/
static void
do_numeric_accum(NumericAggState *state, Numeric newval)
{
NumericVar X;
NumericVar X2;
MemoryContext old_context;
/* Count NaN inputs separately from all else */
if (NUMERIC_IS_NAN(newval))
{
state->NaNcount++;
return;
}
/* load processed number in short-lived context */
init_var_from_num(newval, &X);
/*
* Track the highest input dscale that we've seen, to support inverse
* transitions (see do_numeric_discard).
*/
if (X.dscale > state->maxScale)
{
state->maxScale = X.dscale;
state->maxScaleCount = 1;
}
else if (X.dscale == state->maxScale)
state->maxScaleCount++;
/* if we need X^2, calculate that in short-lived context */
if (state->calcSumX2)
{
init_var(&X2);
mul_var(&X, &X, &X2, X.dscale * 2);
}
/* The rest of this needs to work in the aggregate context */
old_context = MemoryContextSwitchTo(state->agg_context);
state->N++;
/* Accumulate sums */
accum_sum_add(&(state->sumX), &X);
if (state->calcSumX2)
accum_sum_add(&(state->sumX2), &X2);
MemoryContextSwitchTo(old_context);
}
/*
* Attempt to remove an input value from the aggregated state.
*
* If the value cannot be removed then the function will return false; the
* possible reasons for failing are described below.
*
* If we aggregate the values 1.01 and 2 then the result will be 3.01.
* If we are then asked to un-aggregate the 1.01 then we must fail as we
* won't be able to tell what the new aggregated value's dscale should be.
* We don't want to return 2.00 (dscale = 2), since the sum's dscale would
* have been zero if we'd really aggregated only 2.
*
* Note: alternatively, we could count the number of inputs with each possible
* dscale (up to some sane limit). Not yet clear if it's worth the trouble.
*/
static bool
do_numeric_discard(NumericAggState *state, Numeric newval)
{
NumericVar X;
NumericVar X2;
MemoryContext old_context;
/* Count NaN inputs separately from all else */
if (NUMERIC_IS_NAN(newval))
{
state->NaNcount--;
return true;
}
/* load processed number in short-lived context */
init_var_from_num(newval, &X);
/*
* state->sumX's dscale is the maximum dscale of any of the inputs.
* Removing the last input with that dscale would require us to recompute
* the maximum dscale of the *remaining* inputs, which we cannot do unless
* no more non-NaN inputs remain at all. So we report a failure instead,
* and force the aggregation to be redone from scratch.
*/
if (X.dscale == state->maxScale)
{
if (state->maxScaleCount > 1 || state->maxScale == 0)
{
/*
* Some remaining inputs have same dscale, or dscale hasn't gotten
* above zero anyway
*/
state->maxScaleCount--;
}
else if (state->N == 1)
{
/* No remaining non-NaN inputs at all, so reset maxScale */
state->maxScale = 0;
state->maxScaleCount = 0;
}
else
{
/* Correct new maxScale is uncertain, must fail */
return false;
}
}
/* if we need X^2, calculate that in short-lived context */
if (state->calcSumX2)
{
init_var(&X2);
mul_var(&X, &X, &X2, X.dscale * 2);
}
/* The rest of this needs to work in the aggregate context */
old_context = MemoryContextSwitchTo(state->agg_context);
if (state->N-- > 1)
{
/* Negate X, to subtract it from the sum */
X.sign = (X.sign == NUMERIC_POS ? NUMERIC_NEG : NUMERIC_POS);
accum_sum_add(&(state->sumX), &X);
if (state->calcSumX2)
{
/* Negate X^2. X^2 is always positive */
X2.sign = NUMERIC_NEG;
accum_sum_add(&(state->sumX2), &X2);
}
}
else
{
/* Zero the sums */
Assert(state->N == 0);
accum_sum_reset(&state->sumX);
if (state->calcSumX2)
accum_sum_reset(&state->sumX2);
}
MemoryContextSwitchTo(old_context);
return true;
}
/*
* Generic transition function for numeric aggregates that require sumX2.
*/
Datum
numeric_accum(PG_FUNCTION_ARGS)
{
NumericAggState *state;
state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
/* Create the state data on the first call */
if (state == NULL)
state = makeNumericAggState(fcinfo, true);
if (!PG_ARGISNULL(1))
do_numeric_accum(state, PG_GETARG_NUMERIC(1));
PG_RETURN_POINTER(state);
}
/*
* Generic combine function for numeric aggregates which require sumX2
*/
Datum
numeric_combine(PG_FUNCTION_ARGS)
{
2016-06-10 00:02:36 +02:00
NumericAggState *state1;
NumericAggState *state2;
MemoryContext agg_context;
MemoryContext old_context;
if (!AggCheckCallContext(fcinfo, &agg_context))
elog(ERROR, "aggregate function called in non-aggregate context");
state1 = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
state2 = PG_ARGISNULL(1) ? NULL : (NumericAggState *) PG_GETARG_POINTER(1);
if (state2 == NULL)
PG_RETURN_POINTER(state1);
/* manually copy all fields from state2 to state1 */
if (state1 == NULL)
{
old_context = MemoryContextSwitchTo(agg_context);
state1 = makeNumericAggStateCurrentContext(true);
state1->N = state2->N;
state1->NaNcount = state2->NaNcount;
state1->maxScale = state2->maxScale;
state1->maxScaleCount = state2->maxScaleCount;
accum_sum_copy(&state1->sumX, &state2->sumX);
accum_sum_copy(&state1->sumX2, &state2->sumX2);
MemoryContextSwitchTo(old_context);
PG_RETURN_POINTER(state1);
}
if (state2->N > 0)
{
state1->N += state2->N;
state1->NaNcount += state2->NaNcount;
/*
2016-06-10 00:02:36 +02:00
* These are currently only needed for moving aggregates, but let's do
* the right thing anyway...
*/
if (state2->maxScale > state1->maxScale)
{
state1->maxScale = state2->maxScale;
state1->maxScaleCount = state2->maxScaleCount;
}
else if (state2->maxScale == state1->maxScale)
state1->maxScaleCount += state2->maxScaleCount;
/* The rest of this needs to work in the aggregate context */
old_context = MemoryContextSwitchTo(agg_context);
/* Accumulate sums */
accum_sum_combine(&state1->sumX, &state2->sumX);
accum_sum_combine(&state1->sumX2, &state2->sumX2);
MemoryContextSwitchTo(old_context);
}
PG_RETURN_POINTER(state1);
}
/*
* Generic transition function for numeric aggregates that don't require sumX2.
*/
Datum
numeric_avg_accum(PG_FUNCTION_ARGS)
{
NumericAggState *state;
state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
/* Create the state data on the first call */
if (state == NULL)
state = makeNumericAggState(fcinfo, false);
if (!PG_ARGISNULL(1))
do_numeric_accum(state, PG_GETARG_NUMERIC(1));
PG_RETURN_POINTER(state);
}
/*
* Combine function for numeric aggregates which don't require sumX2
*/
Datum
numeric_avg_combine(PG_FUNCTION_ARGS)
{
2016-06-10 00:02:36 +02:00
NumericAggState *state1;
NumericAggState *state2;
MemoryContext agg_context;
MemoryContext old_context;
if (!AggCheckCallContext(fcinfo, &agg_context))
elog(ERROR, "aggregate function called in non-aggregate context");
state1 = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
state2 = PG_ARGISNULL(1) ? NULL : (NumericAggState *) PG_GETARG_POINTER(1);
if (state2 == NULL)
PG_RETURN_POINTER(state1);
/* manually copy all fields from state2 to state1 */
if (state1 == NULL)
{
old_context = MemoryContextSwitchTo(agg_context);
state1 = makeNumericAggStateCurrentContext(false);
state1->N = state2->N;
state1->NaNcount = state2->NaNcount;
state1->maxScale = state2->maxScale;
state1->maxScaleCount = state2->maxScaleCount;
accum_sum_copy(&state1->sumX, &state2->sumX);
MemoryContextSwitchTo(old_context);
PG_RETURN_POINTER(state1);
}
if (state2->N > 0)
{
state1->N += state2->N;
state1->NaNcount += state2->NaNcount;
/*
2016-06-10 00:02:36 +02:00
* These are currently only needed for moving aggregates, but let's do
* the right thing anyway...
*/
if (state2->maxScale > state1->maxScale)
{
state1->maxScale = state2->maxScale;
state1->maxScaleCount = state2->maxScaleCount;
}
else if (state2->maxScale == state1->maxScale)
state1->maxScaleCount += state2->maxScaleCount;
/* The rest of this needs to work in the aggregate context */
old_context = MemoryContextSwitchTo(agg_context);
/* Accumulate sums */
accum_sum_combine(&state1->sumX, &state2->sumX);
MemoryContextSwitchTo(old_context);
}
PG_RETURN_POINTER(state1);
}
/*
* numeric_avg_serialize
* Serialize NumericAggState for numeric aggregates that don't require
Fix type-safety problem with parallel aggregate serial/deserialization. The original specification for this called for the deserialization function to have signature "deserialize(serialtype) returns transtype", which is a security violation if transtype is INTERNAL (which it always would be in practice) and serialtype is not (which ditto). The patch blithely overrode the opr_sanity check for that, which was sloppy-enough work in itself, but the indisputable reason this cannot be allowed to stand is that CREATE FUNCTION will reject such a signature and thus it'd be impossible for extensions to create parallelizable aggregates. The minimum fix to make the signature type-safe is to add a second, dummy argument of type INTERNAL. But to lock it down a bit more and make misuse of INTERNAL-accepting functions less likely, let's get rid of the ability to specify a "serialtype" for an aggregate and just say that the only useful serialtype is BYTEA --- which, in practice, is the only interesting value anyway, due to the usefulness of the send/recv infrastructure for this purpose. That means we only have to allow "serialize(internal) returns bytea" and "deserialize(bytea, internal) returns internal" as the signatures for these support functions. In passing fix bogus signature of int4_avg_combine, which I found thanks to adding an opr_sanity check on combinefunc signatures. catversion bump due to removing pg_aggregate.aggserialtype and adjusting signatures of assorted built-in functions. David Rowley and Tom Lane Discussion: <27247.1466185504@sss.pgh.pa.us>
2016-06-22 22:52:41 +02:00
* sumX2.
*/
Datum
numeric_avg_serialize(PG_FUNCTION_ARGS)
{
2016-06-10 00:02:36 +02:00
NumericAggState *state;
StringInfoData buf;
Datum temp;
bytea *sumX;
bytea *result;
NumericVar tmp_var;
/* Ensure we disallow calling when not in aggregate context */
if (!AggCheckCallContext(fcinfo, NULL))
elog(ERROR, "aggregate function called in non-aggregate context");
state = (NumericAggState *) PG_GETARG_POINTER(0);
/*
* This is a little wasteful since make_result converts the NumericVar
* into a Numeric and numeric_send converts it back again. Is it worth
* splitting the tasks in numeric_send into separate functions to stop
* this? Doing so would also remove the fmgr call overhead.
*/
init_var(&tmp_var);
accum_sum_final(&state->sumX, &tmp_var);
temp = DirectFunctionCall1(numeric_send,
NumericGetDatum(make_result(&tmp_var)));
sumX = DatumGetByteaPP(temp);
free_var(&tmp_var);
pq_begintypsend(&buf);
/* N */
pq_sendint64(&buf, state->N);
/* sumX */
pq_sendbytes(&buf, VARDATA_ANY(sumX), VARSIZE_ANY_EXHDR(sumX));
/* maxScale */
pq_sendint32(&buf, state->maxScale);
/* maxScaleCount */
pq_sendint64(&buf, state->maxScaleCount);
/* NaNcount */
pq_sendint64(&buf, state->NaNcount);
result = pq_endtypsend(&buf);
PG_RETURN_BYTEA_P(result);
}
/*
* numeric_avg_deserialize
Fix type-safety problem with parallel aggregate serial/deserialization. The original specification for this called for the deserialization function to have signature "deserialize(serialtype) returns transtype", which is a security violation if transtype is INTERNAL (which it always would be in practice) and serialtype is not (which ditto). The patch blithely overrode the opr_sanity check for that, which was sloppy-enough work in itself, but the indisputable reason this cannot be allowed to stand is that CREATE FUNCTION will reject such a signature and thus it'd be impossible for extensions to create parallelizable aggregates. The minimum fix to make the signature type-safe is to add a second, dummy argument of type INTERNAL. But to lock it down a bit more and make misuse of INTERNAL-accepting functions less likely, let's get rid of the ability to specify a "serialtype" for an aggregate and just say that the only useful serialtype is BYTEA --- which, in practice, is the only interesting value anyway, due to the usefulness of the send/recv infrastructure for this purpose. That means we only have to allow "serialize(internal) returns bytea" and "deserialize(bytea, internal) returns internal" as the signatures for these support functions. In passing fix bogus signature of int4_avg_combine, which I found thanks to adding an opr_sanity check on combinefunc signatures. catversion bump due to removing pg_aggregate.aggserialtype and adjusting signatures of assorted built-in functions. David Rowley and Tom Lane Discussion: <27247.1466185504@sss.pgh.pa.us>
2016-06-22 22:52:41 +02:00
* Deserialize bytea into NumericAggState for numeric aggregates that
* don't require sumX2.
*/
Datum
numeric_avg_deserialize(PG_FUNCTION_ARGS)
{
Fix type-safety problem with parallel aggregate serial/deserialization. The original specification for this called for the deserialization function to have signature "deserialize(serialtype) returns transtype", which is a security violation if transtype is INTERNAL (which it always would be in practice) and serialtype is not (which ditto). The patch blithely overrode the opr_sanity check for that, which was sloppy-enough work in itself, but the indisputable reason this cannot be allowed to stand is that CREATE FUNCTION will reject such a signature and thus it'd be impossible for extensions to create parallelizable aggregates. The minimum fix to make the signature type-safe is to add a second, dummy argument of type INTERNAL. But to lock it down a bit more and make misuse of INTERNAL-accepting functions less likely, let's get rid of the ability to specify a "serialtype" for an aggregate and just say that the only useful serialtype is BYTEA --- which, in practice, is the only interesting value anyway, due to the usefulness of the send/recv infrastructure for this purpose. That means we only have to allow "serialize(internal) returns bytea" and "deserialize(bytea, internal) returns internal" as the signatures for these support functions. In passing fix bogus signature of int4_avg_combine, which I found thanks to adding an opr_sanity check on combinefunc signatures. catversion bump due to removing pg_aggregate.aggserialtype and adjusting signatures of assorted built-in functions. David Rowley and Tom Lane Discussion: <27247.1466185504@sss.pgh.pa.us>
2016-06-22 22:52:41 +02:00
bytea *sstate;
2016-06-10 00:02:36 +02:00
NumericAggState *result;
Datum temp;
NumericVar tmp_var;
2016-06-10 00:02:36 +02:00
StringInfoData buf;
if (!AggCheckCallContext(fcinfo, NULL))
elog(ERROR, "aggregate function called in non-aggregate context");
sstate = PG_GETARG_BYTEA_PP(0);
Fix type-safety problem with parallel aggregate serial/deserialization. The original specification for this called for the deserialization function to have signature "deserialize(serialtype) returns transtype", which is a security violation if transtype is INTERNAL (which it always would be in practice) and serialtype is not (which ditto). The patch blithely overrode the opr_sanity check for that, which was sloppy-enough work in itself, but the indisputable reason this cannot be allowed to stand is that CREATE FUNCTION will reject such a signature and thus it'd be impossible for extensions to create parallelizable aggregates. The minimum fix to make the signature type-safe is to add a second, dummy argument of type INTERNAL. But to lock it down a bit more and make misuse of INTERNAL-accepting functions less likely, let's get rid of the ability to specify a "serialtype" for an aggregate and just say that the only useful serialtype is BYTEA --- which, in practice, is the only interesting value anyway, due to the usefulness of the send/recv infrastructure for this purpose. That means we only have to allow "serialize(internal) returns bytea" and "deserialize(bytea, internal) returns internal" as the signatures for these support functions. In passing fix bogus signature of int4_avg_combine, which I found thanks to adding an opr_sanity check on combinefunc signatures. catversion bump due to removing pg_aggregate.aggserialtype and adjusting signatures of assorted built-in functions. David Rowley and Tom Lane Discussion: <27247.1466185504@sss.pgh.pa.us>
2016-06-22 22:52:41 +02:00
/*
* Copy the bytea into a StringInfo so that we can "receive" it using the
* standard recv-function infrastructure.
*/
initStringInfo(&buf);
appendBinaryStringInfo(&buf,
VARDATA_ANY(sstate), VARSIZE_ANY_EXHDR(sstate));
result = makeNumericAggStateCurrentContext(false);
/* N */
result->N = pq_getmsgint64(&buf);
/* sumX */
temp = DirectFunctionCall3(numeric_recv,
PointerGetDatum(&buf),
ObjectIdGetDatum(InvalidOid),
Int32GetDatum(-1));
init_var_from_num(DatumGetNumeric(temp), &tmp_var);
accum_sum_add(&(result->sumX), &tmp_var);
/* maxScale */
result->maxScale = pq_getmsgint(&buf, 4);
/* maxScaleCount */
result->maxScaleCount = pq_getmsgint64(&buf);
/* NaNcount */
result->NaNcount = pq_getmsgint64(&buf);
pq_getmsgend(&buf);
pfree(buf.data);
PG_RETURN_POINTER(result);
}
/*
* numeric_serialize
* Serialization function for NumericAggState for numeric aggregates that
Fix type-safety problem with parallel aggregate serial/deserialization. The original specification for this called for the deserialization function to have signature "deserialize(serialtype) returns transtype", which is a security violation if transtype is INTERNAL (which it always would be in practice) and serialtype is not (which ditto). The patch blithely overrode the opr_sanity check for that, which was sloppy-enough work in itself, but the indisputable reason this cannot be allowed to stand is that CREATE FUNCTION will reject such a signature and thus it'd be impossible for extensions to create parallelizable aggregates. The minimum fix to make the signature type-safe is to add a second, dummy argument of type INTERNAL. But to lock it down a bit more and make misuse of INTERNAL-accepting functions less likely, let's get rid of the ability to specify a "serialtype" for an aggregate and just say that the only useful serialtype is BYTEA --- which, in practice, is the only interesting value anyway, due to the usefulness of the send/recv infrastructure for this purpose. That means we only have to allow "serialize(internal) returns bytea" and "deserialize(bytea, internal) returns internal" as the signatures for these support functions. In passing fix bogus signature of int4_avg_combine, which I found thanks to adding an opr_sanity check on combinefunc signatures. catversion bump due to removing pg_aggregate.aggserialtype and adjusting signatures of assorted built-in functions. David Rowley and Tom Lane Discussion: <27247.1466185504@sss.pgh.pa.us>
2016-06-22 22:52:41 +02:00
* require sumX2.
*/
Datum
numeric_serialize(PG_FUNCTION_ARGS)
{
2016-06-10 00:02:36 +02:00
NumericAggState *state;
StringInfoData buf;
Datum temp;
bytea *sumX;
NumericVar tmp_var;
2016-06-10 00:02:36 +02:00
bytea *sumX2;
bytea *result;
/* Ensure we disallow calling when not in aggregate context */
if (!AggCheckCallContext(fcinfo, NULL))
elog(ERROR, "aggregate function called in non-aggregate context");
state = (NumericAggState *) PG_GETARG_POINTER(0);
/*
* This is a little wasteful since make_result converts the NumericVar
* into a Numeric and numeric_send converts it back again. Is it worth
* splitting the tasks in numeric_send into separate functions to stop
* this? Doing so would also remove the fmgr call overhead.
*/
init_var(&tmp_var);
accum_sum_final(&state->sumX, &tmp_var);
temp = DirectFunctionCall1(numeric_send,
NumericGetDatum(make_result(&tmp_var)));
sumX = DatumGetByteaPP(temp);
accum_sum_final(&state->sumX2, &tmp_var);
temp = DirectFunctionCall1(numeric_send,
NumericGetDatum(make_result(&tmp_var)));
sumX2 = DatumGetByteaPP(temp);
free_var(&tmp_var);
pq_begintypsend(&buf);
/* N */
pq_sendint64(&buf, state->N);
/* sumX */
pq_sendbytes(&buf, VARDATA_ANY(sumX), VARSIZE_ANY_EXHDR(sumX));
/* sumX2 */
pq_sendbytes(&buf, VARDATA_ANY(sumX2), VARSIZE_ANY_EXHDR(sumX2));
/* maxScale */
pq_sendint32(&buf, state->maxScale);
/* maxScaleCount */
pq_sendint64(&buf, state->maxScaleCount);
/* NaNcount */
pq_sendint64(&buf, state->NaNcount);
result = pq_endtypsend(&buf);
PG_RETURN_BYTEA_P(result);
}
/*
* numeric_deserialize
* Deserialization function for NumericAggState for numeric aggregates that
Fix type-safety problem with parallel aggregate serial/deserialization. The original specification for this called for the deserialization function to have signature "deserialize(serialtype) returns transtype", which is a security violation if transtype is INTERNAL (which it always would be in practice) and serialtype is not (which ditto). The patch blithely overrode the opr_sanity check for that, which was sloppy-enough work in itself, but the indisputable reason this cannot be allowed to stand is that CREATE FUNCTION will reject such a signature and thus it'd be impossible for extensions to create parallelizable aggregates. The minimum fix to make the signature type-safe is to add a second, dummy argument of type INTERNAL. But to lock it down a bit more and make misuse of INTERNAL-accepting functions less likely, let's get rid of the ability to specify a "serialtype" for an aggregate and just say that the only useful serialtype is BYTEA --- which, in practice, is the only interesting value anyway, due to the usefulness of the send/recv infrastructure for this purpose. That means we only have to allow "serialize(internal) returns bytea" and "deserialize(bytea, internal) returns internal" as the signatures for these support functions. In passing fix bogus signature of int4_avg_combine, which I found thanks to adding an opr_sanity check on combinefunc signatures. catversion bump due to removing pg_aggregate.aggserialtype and adjusting signatures of assorted built-in functions. David Rowley and Tom Lane Discussion: <27247.1466185504@sss.pgh.pa.us>
2016-06-22 22:52:41 +02:00
* require sumX2.
*/
Datum
numeric_deserialize(PG_FUNCTION_ARGS)
{
Fix type-safety problem with parallel aggregate serial/deserialization. The original specification for this called for the deserialization function to have signature "deserialize(serialtype) returns transtype", which is a security violation if transtype is INTERNAL (which it always would be in practice) and serialtype is not (which ditto). The patch blithely overrode the opr_sanity check for that, which was sloppy-enough work in itself, but the indisputable reason this cannot be allowed to stand is that CREATE FUNCTION will reject such a signature and thus it'd be impossible for extensions to create parallelizable aggregates. The minimum fix to make the signature type-safe is to add a second, dummy argument of type INTERNAL. But to lock it down a bit more and make misuse of INTERNAL-accepting functions less likely, let's get rid of the ability to specify a "serialtype" for an aggregate and just say that the only useful serialtype is BYTEA --- which, in practice, is the only interesting value anyway, due to the usefulness of the send/recv infrastructure for this purpose. That means we only have to allow "serialize(internal) returns bytea" and "deserialize(bytea, internal) returns internal" as the signatures for these support functions. In passing fix bogus signature of int4_avg_combine, which I found thanks to adding an opr_sanity check on combinefunc signatures. catversion bump due to removing pg_aggregate.aggserialtype and adjusting signatures of assorted built-in functions. David Rowley and Tom Lane Discussion: <27247.1466185504@sss.pgh.pa.us>
2016-06-22 22:52:41 +02:00
bytea *sstate;
2016-06-10 00:02:36 +02:00
NumericAggState *result;
Datum temp;
NumericVar sumX_var;
NumericVar sumX2_var;
2016-06-10 00:02:36 +02:00
StringInfoData buf;
if (!AggCheckCallContext(fcinfo, NULL))
elog(ERROR, "aggregate function called in non-aggregate context");
sstate = PG_GETARG_BYTEA_PP(0);
Fix type-safety problem with parallel aggregate serial/deserialization. The original specification for this called for the deserialization function to have signature "deserialize(serialtype) returns transtype", which is a security violation if transtype is INTERNAL (which it always would be in practice) and serialtype is not (which ditto). The patch blithely overrode the opr_sanity check for that, which was sloppy-enough work in itself, but the indisputable reason this cannot be allowed to stand is that CREATE FUNCTION will reject such a signature and thus it'd be impossible for extensions to create parallelizable aggregates. The minimum fix to make the signature type-safe is to add a second, dummy argument of type INTERNAL. But to lock it down a bit more and make misuse of INTERNAL-accepting functions less likely, let's get rid of the ability to specify a "serialtype" for an aggregate and just say that the only useful serialtype is BYTEA --- which, in practice, is the only interesting value anyway, due to the usefulness of the send/recv infrastructure for this purpose. That means we only have to allow "serialize(internal) returns bytea" and "deserialize(bytea, internal) returns internal" as the signatures for these support functions. In passing fix bogus signature of int4_avg_combine, which I found thanks to adding an opr_sanity check on combinefunc signatures. catversion bump due to removing pg_aggregate.aggserialtype and adjusting signatures of assorted built-in functions. David Rowley and Tom Lane Discussion: <27247.1466185504@sss.pgh.pa.us>
2016-06-22 22:52:41 +02:00
/*
* Copy the bytea into a StringInfo so that we can "receive" it using the
* standard recv-function infrastructure.
*/
initStringInfo(&buf);
appendBinaryStringInfo(&buf,
VARDATA_ANY(sstate), VARSIZE_ANY_EXHDR(sstate));
result = makeNumericAggStateCurrentContext(false);
/* N */
result->N = pq_getmsgint64(&buf);
/* sumX */
temp = DirectFunctionCall3(numeric_recv,
PointerGetDatum(&buf),
ObjectIdGetDatum(InvalidOid),
Int32GetDatum(-1));
init_var_from_num(DatumGetNumeric(temp), &sumX_var);
accum_sum_add(&(result->sumX), &sumX_var);
/* sumX2 */
temp = DirectFunctionCall3(numeric_recv,
PointerGetDatum(&buf),
ObjectIdGetDatum(InvalidOid),
Int32GetDatum(-1));
init_var_from_num(DatumGetNumeric(temp), &sumX2_var);
accum_sum_add(&(result->sumX2), &sumX2_var);
/* maxScale */
result->maxScale = pq_getmsgint(&buf, 4);
/* maxScaleCount */
result->maxScaleCount = pq_getmsgint64(&buf);
/* NaNcount */
result->NaNcount = pq_getmsgint64(&buf);
pq_getmsgend(&buf);
pfree(buf.data);
PG_RETURN_POINTER(result);
}
/*
* Generic inverse transition function for numeric aggregates
* (with or without requirement for X^2).
*/
Datum
numeric_accum_inv(PG_FUNCTION_ARGS)
{
NumericAggState *state;
state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
/* Should not get here with no state */
if (state == NULL)
elog(ERROR, "numeric_accum_inv called with NULL state");
if (!PG_ARGISNULL(1))
{
/* If we fail to perform the inverse transition, return NULL */
if (!do_numeric_discard(state, PG_GETARG_NUMERIC(1)))
PG_RETURN_NULL();
}
PG_RETURN_POINTER(state);
}
/*
* Integer data types in general use Numeric accumulators to share code
* and avoid risk of overflow.
*
* However for performance reasons optimized special-purpose accumulator
* routines are used when possible.
*
* On platforms with 128-bit integer support, the 128-bit routines will be
* used when sum(X) or sum(X*X) fit into 128-bit.
*
* For 16 and 32 bit inputs, the N and sum(X) fit into 64-bit so the 64-bit
* accumulators will be used for SUM and AVG of these data types.
*/
#ifdef HAVE_INT128
typedef struct Int128AggState
{
2015-05-24 03:35:49 +02:00
bool calcSumX2; /* if true, calculate sumX2 */
int64 N; /* count of processed numbers */
int128 sumX; /* sum of processed numbers */
int128 sumX2; /* sum of squares of processed numbers */
} Int128AggState;
/*
* Prepare state data for a 128-bit aggregate function that needs to compute
* sum, count and optionally sum of squares of the input.
*/
static Int128AggState *
makeInt128AggState(FunctionCallInfo fcinfo, bool calcSumX2)
{
Int128AggState *state;
MemoryContext agg_context;
MemoryContext old_context;
if (!AggCheckCallContext(fcinfo, &agg_context))
elog(ERROR, "aggregate function called in non-aggregate context");
old_context = MemoryContextSwitchTo(agg_context);
state = (Int128AggState *) palloc0(sizeof(Int128AggState));
state->calcSumX2 = calcSumX2;
MemoryContextSwitchTo(old_context);
return state;
}
/*
* Like makeInt128AggState(), but allocate the state in the current memory
* context.
*/
static Int128AggState *
makeInt128AggStateCurrentContext(bool calcSumX2)
{
Int128AggState *state;
state = (Int128AggState *) palloc0(sizeof(Int128AggState));
state->calcSumX2 = calcSumX2;
return state;
}
/*
* Accumulate a new input value for 128-bit aggregate functions.
*/
static void
do_int128_accum(Int128AggState *state, int128 newval)
{
if (state->calcSumX2)
state->sumX2 += newval * newval;
state->sumX += newval;
state->N++;
}
/*
* Remove an input value from the aggregated state.
*/
static void
do_int128_discard(Int128AggState *state, int128 newval)
{
if (state->calcSumX2)
state->sumX2 -= newval * newval;
state->sumX -= newval;
state->N--;
}
typedef Int128AggState PolyNumAggState;
#define makePolyNumAggState makeInt128AggState
#define makePolyNumAggStateCurrentContext makeInt128AggStateCurrentContext
#else
typedef NumericAggState PolyNumAggState;
#define makePolyNumAggState makeNumericAggState
#define makePolyNumAggStateCurrentContext makeNumericAggStateCurrentContext
#endif
Datum
int2_accum(PG_FUNCTION_ARGS)
{
PolyNumAggState *state;
state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0);
/* Create the state data on the first call */
if (state == NULL)
state = makePolyNumAggState(fcinfo, true);
if (!PG_ARGISNULL(1))
{
#ifdef HAVE_INT128
do_int128_accum(state, (int128) PG_GETARG_INT16(1));
#else
Numeric newval;
newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric,
PG_GETARG_DATUM(1)));
do_numeric_accum(state, newval);
#endif
}
PG_RETURN_POINTER(state);
}
Datum
int4_accum(PG_FUNCTION_ARGS)
{
PolyNumAggState *state;
state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0);
/* Create the state data on the first call */
if (state == NULL)
state = makePolyNumAggState(fcinfo, true);
if (!PG_ARGISNULL(1))
{
#ifdef HAVE_INT128
do_int128_accum(state, (int128) PG_GETARG_INT32(1));
#else
Numeric newval;
newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
PG_GETARG_DATUM(1)));
do_numeric_accum(state, newval);
#endif
}
PG_RETURN_POINTER(state);
}
Datum
int8_accum(PG_FUNCTION_ARGS)
{
NumericAggState *state;
state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
/* Create the state data on the first call */
if (state == NULL)
state = makeNumericAggState(fcinfo, true);
if (!PG_ARGISNULL(1))
{
Numeric newval;
newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
PG_GETARG_DATUM(1)));
do_numeric_accum(state, newval);
}
PG_RETURN_POINTER(state);
}
/*
* Combine function for numeric aggregates which require sumX2
*/
Datum
numeric_poly_combine(PG_FUNCTION_ARGS)
{
PolyNumAggState *state1;
PolyNumAggState *state2;
2016-06-10 00:02:36 +02:00
MemoryContext agg_context;
MemoryContext old_context;
if (!AggCheckCallContext(fcinfo, &agg_context))
elog(ERROR, "aggregate function called in non-aggregate context");
state1 = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0);
state2 = PG_ARGISNULL(1) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(1);
if (state2 == NULL)
PG_RETURN_POINTER(state1);
/* manually copy all fields from state2 to state1 */
if (state1 == NULL)
{
old_context = MemoryContextSwitchTo(agg_context);
state1 = makePolyNumAggState(fcinfo, true);
state1->N = state2->N;
#ifdef HAVE_INT128
state1->sumX = state2->sumX;
state1->sumX2 = state2->sumX2;
#else
accum_sum_copy(&state1->sumX, &state2->sumX);
accum_sum_copy(&state1->sumX2, &state2->sumX2);
#endif
MemoryContextSwitchTo(old_context);
PG_RETURN_POINTER(state1);
}
if (state2->N > 0)
{
state1->N += state2->N;
#ifdef HAVE_INT128
state1->sumX += state2->sumX;
state1->sumX2 += state2->sumX2;
#else
/* The rest of this needs to work in the aggregate context */
old_context = MemoryContextSwitchTo(agg_context);
/* Accumulate sums */
accum_sum_combine(&state1->sumX, &state2->sumX);
accum_sum_combine(&state1->sumX2, &state2->sumX2);
MemoryContextSwitchTo(old_context);
#endif
}
PG_RETURN_POINTER(state1);
}
/*
* numeric_poly_serialize
Fix type-safety problem with parallel aggregate serial/deserialization. The original specification for this called for the deserialization function to have signature "deserialize(serialtype) returns transtype", which is a security violation if transtype is INTERNAL (which it always would be in practice) and serialtype is not (which ditto). The patch blithely overrode the opr_sanity check for that, which was sloppy-enough work in itself, but the indisputable reason this cannot be allowed to stand is that CREATE FUNCTION will reject such a signature and thus it'd be impossible for extensions to create parallelizable aggregates. The minimum fix to make the signature type-safe is to add a second, dummy argument of type INTERNAL. But to lock it down a bit more and make misuse of INTERNAL-accepting functions less likely, let's get rid of the ability to specify a "serialtype" for an aggregate and just say that the only useful serialtype is BYTEA --- which, in practice, is the only interesting value anyway, due to the usefulness of the send/recv infrastructure for this purpose. That means we only have to allow "serialize(internal) returns bytea" and "deserialize(bytea, internal) returns internal" as the signatures for these support functions. In passing fix bogus signature of int4_avg_combine, which I found thanks to adding an opr_sanity check on combinefunc signatures. catversion bump due to removing pg_aggregate.aggserialtype and adjusting signatures of assorted built-in functions. David Rowley and Tom Lane Discussion: <27247.1466185504@sss.pgh.pa.us>
2016-06-22 22:52:41 +02:00
* Serialize PolyNumAggState into bytea for aggregate functions which
* require sumX2.
*/
Datum
numeric_poly_serialize(PG_FUNCTION_ARGS)
{
2016-06-10 00:02:36 +02:00
PolyNumAggState *state;
StringInfoData buf;
bytea *sumX;
bytea *sumX2;
bytea *result;
/* Ensure we disallow calling when not in aggregate context */
if (!AggCheckCallContext(fcinfo, NULL))
elog(ERROR, "aggregate function called in non-aggregate context");
state = (PolyNumAggState *) PG_GETARG_POINTER(0);
/*
* If the platform supports int128 then sumX and sumX2 will be a 128 bit
* integer type. Here we'll convert that into a numeric type so that the
* combine state is in the same format for both int128 enabled machines
* and machines which don't support that type. The logic here is that one
* day we might like to send these over to another server for further
* processing and we want a standard format to work with.
*/
{
Datum temp;
NumericVar num;
init_var(&num);
#ifdef HAVE_INT128
int128_to_numericvar(state->sumX, &num);
#else
accum_sum_final(&state->sumX, &num);
#endif
temp = DirectFunctionCall1(numeric_send,
NumericGetDatum(make_result(&num)));
sumX = DatumGetByteaPP(temp);
#ifdef HAVE_INT128
int128_to_numericvar(state->sumX2, &num);
#else
accum_sum_final(&state->sumX2, &num);
#endif
temp = DirectFunctionCall1(numeric_send,
NumericGetDatum(make_result(&num)));
sumX2 = DatumGetByteaPP(temp);
free_var(&num);
}
pq_begintypsend(&buf);
/* N */
pq_sendint64(&buf, state->N);
/* sumX */
pq_sendbytes(&buf, VARDATA_ANY(sumX), VARSIZE_ANY_EXHDR(sumX));
/* sumX2 */
pq_sendbytes(&buf, VARDATA_ANY(sumX2), VARSIZE_ANY_EXHDR(sumX2));
result = pq_endtypsend(&buf);
PG_RETURN_BYTEA_P(result);
}
/*
* numeric_poly_deserialize
Fix type-safety problem with parallel aggregate serial/deserialization. The original specification for this called for the deserialization function to have signature "deserialize(serialtype) returns transtype", which is a security violation if transtype is INTERNAL (which it always would be in practice) and serialtype is not (which ditto). The patch blithely overrode the opr_sanity check for that, which was sloppy-enough work in itself, but the indisputable reason this cannot be allowed to stand is that CREATE FUNCTION will reject such a signature and thus it'd be impossible for extensions to create parallelizable aggregates. The minimum fix to make the signature type-safe is to add a second, dummy argument of type INTERNAL. But to lock it down a bit more and make misuse of INTERNAL-accepting functions less likely, let's get rid of the ability to specify a "serialtype" for an aggregate and just say that the only useful serialtype is BYTEA --- which, in practice, is the only interesting value anyway, due to the usefulness of the send/recv infrastructure for this purpose. That means we only have to allow "serialize(internal) returns bytea" and "deserialize(bytea, internal) returns internal" as the signatures for these support functions. In passing fix bogus signature of int4_avg_combine, which I found thanks to adding an opr_sanity check on combinefunc signatures. catversion bump due to removing pg_aggregate.aggserialtype and adjusting signatures of assorted built-in functions. David Rowley and Tom Lane Discussion: <27247.1466185504@sss.pgh.pa.us>
2016-06-22 22:52:41 +02:00
* Deserialize PolyNumAggState from bytea for aggregate functions which
* require sumX2.
*/
Datum
numeric_poly_deserialize(PG_FUNCTION_ARGS)
{
Fix type-safety problem with parallel aggregate serial/deserialization. The original specification for this called for the deserialization function to have signature "deserialize(serialtype) returns transtype", which is a security violation if transtype is INTERNAL (which it always would be in practice) and serialtype is not (which ditto). The patch blithely overrode the opr_sanity check for that, which was sloppy-enough work in itself, but the indisputable reason this cannot be allowed to stand is that CREATE FUNCTION will reject such a signature and thus it'd be impossible for extensions to create parallelizable aggregates. The minimum fix to make the signature type-safe is to add a second, dummy argument of type INTERNAL. But to lock it down a bit more and make misuse of INTERNAL-accepting functions less likely, let's get rid of the ability to specify a "serialtype" for an aggregate and just say that the only useful serialtype is BYTEA --- which, in practice, is the only interesting value anyway, due to the usefulness of the send/recv infrastructure for this purpose. That means we only have to allow "serialize(internal) returns bytea" and "deserialize(bytea, internal) returns internal" as the signatures for these support functions. In passing fix bogus signature of int4_avg_combine, which I found thanks to adding an opr_sanity check on combinefunc signatures. catversion bump due to removing pg_aggregate.aggserialtype and adjusting signatures of assorted built-in functions. David Rowley and Tom Lane Discussion: <27247.1466185504@sss.pgh.pa.us>
2016-06-22 22:52:41 +02:00
bytea *sstate;
2016-06-10 00:02:36 +02:00
PolyNumAggState *result;
Datum sumX;
NumericVar sumX_var;
2016-06-10 00:02:36 +02:00
Datum sumX2;
NumericVar sumX2_var;
2016-06-10 00:02:36 +02:00
StringInfoData buf;
if (!AggCheckCallContext(fcinfo, NULL))
elog(ERROR, "aggregate function called in non-aggregate context");
sstate = PG_GETARG_BYTEA_PP(0);
Fix type-safety problem with parallel aggregate serial/deserialization. The original specification for this called for the deserialization function to have signature "deserialize(serialtype) returns transtype", which is a security violation if transtype is INTERNAL (which it always would be in practice) and serialtype is not (which ditto). The patch blithely overrode the opr_sanity check for that, which was sloppy-enough work in itself, but the indisputable reason this cannot be allowed to stand is that CREATE FUNCTION will reject such a signature and thus it'd be impossible for extensions to create parallelizable aggregates. The minimum fix to make the signature type-safe is to add a second, dummy argument of type INTERNAL. But to lock it down a bit more and make misuse of INTERNAL-accepting functions less likely, let's get rid of the ability to specify a "serialtype" for an aggregate and just say that the only useful serialtype is BYTEA --- which, in practice, is the only interesting value anyway, due to the usefulness of the send/recv infrastructure for this purpose. That means we only have to allow "serialize(internal) returns bytea" and "deserialize(bytea, internal) returns internal" as the signatures for these support functions. In passing fix bogus signature of int4_avg_combine, which I found thanks to adding an opr_sanity check on combinefunc signatures. catversion bump due to removing pg_aggregate.aggserialtype and adjusting signatures of assorted built-in functions. David Rowley and Tom Lane Discussion: <27247.1466185504@sss.pgh.pa.us>
2016-06-22 22:52:41 +02:00
/*
* Copy the bytea into a StringInfo so that we can "receive" it using the
* standard recv-function infrastructure.
*/
initStringInfo(&buf);
appendBinaryStringInfo(&buf,
VARDATA_ANY(sstate), VARSIZE_ANY_EXHDR(sstate));
result = makePolyNumAggStateCurrentContext(false);
/* N */
result->N = pq_getmsgint64(&buf);
/* sumX */
sumX = DirectFunctionCall3(numeric_recv,
PointerGetDatum(&buf),
ObjectIdGetDatum(InvalidOid),
Int32GetDatum(-1));
/* sumX2 */
sumX2 = DirectFunctionCall3(numeric_recv,
2016-06-10 00:02:36 +02:00
PointerGetDatum(&buf),
ObjectIdGetDatum(InvalidOid),
Int32GetDatum(-1));
init_var_from_num(DatumGetNumeric(sumX), &sumX_var);
#ifdef HAVE_INT128
numericvar_to_int128(&sumX_var, &result->sumX);
#else
accum_sum_add(&result->sumX, &sumX_var);
#endif
init_var_from_num(DatumGetNumeric(sumX2), &sumX2_var);
#ifdef HAVE_INT128
numericvar_to_int128(&sumX2_var, &result->sumX2);
#else
accum_sum_add(&result->sumX2, &sumX2_var);
#endif
pq_getmsgend(&buf);
pfree(buf.data);
PG_RETURN_POINTER(result);
}
/*
* Transition function for int8 input when we don't need sumX2.
*/
Datum
int8_avg_accum(PG_FUNCTION_ARGS)
{
PolyNumAggState *state;
state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0);
/* Create the state data on the first call */
if (state == NULL)
state = makePolyNumAggState(fcinfo, false);
if (!PG_ARGISNULL(1))
{
#ifdef HAVE_INT128
do_int128_accum(state, (int128) PG_GETARG_INT64(1));
#else
Numeric newval;
newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
PG_GETARG_DATUM(1)));
do_numeric_accum(state, newval);
#endif
}
PG_RETURN_POINTER(state);
}
/*
* Combine function for PolyNumAggState for aggregates which don't require
* sumX2
*/
Datum
int8_avg_combine(PG_FUNCTION_ARGS)
{
2016-06-10 00:02:36 +02:00
PolyNumAggState *state1;
PolyNumAggState *state2;
MemoryContext agg_context;
MemoryContext old_context;
if (!AggCheckCallContext(fcinfo, &agg_context))
elog(ERROR, "aggregate function called in non-aggregate context");
state1 = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0);
state2 = PG_ARGISNULL(1) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(1);
if (state2 == NULL)
PG_RETURN_POINTER(state1);
/* manually copy all fields from state2 to state1 */
if (state1 == NULL)
{
old_context = MemoryContextSwitchTo(agg_context);
state1 = makePolyNumAggState(fcinfo, false);
state1->N = state2->N;
#ifdef HAVE_INT128
state1->sumX = state2->sumX;
#else
accum_sum_copy(&state1->sumX, &state2->sumX);
#endif
MemoryContextSwitchTo(old_context);
PG_RETURN_POINTER(state1);
}
if (state2->N > 0)
{
state1->N += state2->N;
#ifdef HAVE_INT128
state1->sumX += state2->sumX;
#else
/* The rest of this needs to work in the aggregate context */
old_context = MemoryContextSwitchTo(agg_context);
/* Accumulate sums */
accum_sum_combine(&state1->sumX, &state2->sumX);
MemoryContextSwitchTo(old_context);
#endif
}
PG_RETURN_POINTER(state1);
}
/*
* int8_avg_serialize
* Serialize PolyNumAggState into bytea using the standard
* recv-function infrastructure.
*/
Datum
int8_avg_serialize(PG_FUNCTION_ARGS)
{
2016-06-10 00:02:36 +02:00
PolyNumAggState *state;
StringInfoData buf;
bytea *sumX;
bytea *result;
/* Ensure we disallow calling when not in aggregate context */
if (!AggCheckCallContext(fcinfo, NULL))
elog(ERROR, "aggregate function called in non-aggregate context");
state = (PolyNumAggState *) PG_GETARG_POINTER(0);
/*
* If the platform supports int128 then sumX will be a 128 integer type.
* Here we'll convert that into a numeric type so that the combine state
* is in the same format for both int128 enabled machines and machines
* which don't support that type. The logic here is that one day we might
* like to send these over to another server for further processing and we
* want a standard format to work with.
*/
{
Datum temp;
NumericVar num;
init_var(&num);
#ifdef HAVE_INT128
int128_to_numericvar(state->sumX, &num);
#else
accum_sum_final(&state->sumX, &num);
#endif
temp = DirectFunctionCall1(numeric_send,
NumericGetDatum(make_result(&num)));
sumX = DatumGetByteaPP(temp);
free_var(&num);
}
pq_begintypsend(&buf);
/* N */
pq_sendint64(&buf, state->N);
/* sumX */
pq_sendbytes(&buf, VARDATA_ANY(sumX), VARSIZE_ANY_EXHDR(sumX));
result = pq_endtypsend(&buf);
PG_RETURN_BYTEA_P(result);
}
/*
* int8_avg_deserialize
* Deserialize bytea back into PolyNumAggState.
*/
Datum
int8_avg_deserialize(PG_FUNCTION_ARGS)
{
Fix type-safety problem with parallel aggregate serial/deserialization. The original specification for this called for the deserialization function to have signature "deserialize(serialtype) returns transtype", which is a security violation if transtype is INTERNAL (which it always would be in practice) and serialtype is not (which ditto). The patch blithely overrode the opr_sanity check for that, which was sloppy-enough work in itself, but the indisputable reason this cannot be allowed to stand is that CREATE FUNCTION will reject such a signature and thus it'd be impossible for extensions to create parallelizable aggregates. The minimum fix to make the signature type-safe is to add a second, dummy argument of type INTERNAL. But to lock it down a bit more and make misuse of INTERNAL-accepting functions less likely, let's get rid of the ability to specify a "serialtype" for an aggregate and just say that the only useful serialtype is BYTEA --- which, in practice, is the only interesting value anyway, due to the usefulness of the send/recv infrastructure for this purpose. That means we only have to allow "serialize(internal) returns bytea" and "deserialize(bytea, internal) returns internal" as the signatures for these support functions. In passing fix bogus signature of int4_avg_combine, which I found thanks to adding an opr_sanity check on combinefunc signatures. catversion bump due to removing pg_aggregate.aggserialtype and adjusting signatures of assorted built-in functions. David Rowley and Tom Lane Discussion: <27247.1466185504@sss.pgh.pa.us>
2016-06-22 22:52:41 +02:00
bytea *sstate;
2016-06-10 00:02:36 +02:00
PolyNumAggState *result;
StringInfoData buf;
Datum temp;
NumericVar num;
if (!AggCheckCallContext(fcinfo, NULL))
elog(ERROR, "aggregate function called in non-aggregate context");
sstate = PG_GETARG_BYTEA_PP(0);
Fix type-safety problem with parallel aggregate serial/deserialization. The original specification for this called for the deserialization function to have signature "deserialize(serialtype) returns transtype", which is a security violation if transtype is INTERNAL (which it always would be in practice) and serialtype is not (which ditto). The patch blithely overrode the opr_sanity check for that, which was sloppy-enough work in itself, but the indisputable reason this cannot be allowed to stand is that CREATE FUNCTION will reject such a signature and thus it'd be impossible for extensions to create parallelizable aggregates. The minimum fix to make the signature type-safe is to add a second, dummy argument of type INTERNAL. But to lock it down a bit more and make misuse of INTERNAL-accepting functions less likely, let's get rid of the ability to specify a "serialtype" for an aggregate and just say that the only useful serialtype is BYTEA --- which, in practice, is the only interesting value anyway, due to the usefulness of the send/recv infrastructure for this purpose. That means we only have to allow "serialize(internal) returns bytea" and "deserialize(bytea, internal) returns internal" as the signatures for these support functions. In passing fix bogus signature of int4_avg_combine, which I found thanks to adding an opr_sanity check on combinefunc signatures. catversion bump due to removing pg_aggregate.aggserialtype and adjusting signatures of assorted built-in functions. David Rowley and Tom Lane Discussion: <27247.1466185504@sss.pgh.pa.us>
2016-06-22 22:52:41 +02:00
/*
* Copy the bytea into a StringInfo so that we can "receive" it using the
* standard recv-function infrastructure.
*/
initStringInfo(&buf);
appendBinaryStringInfo(&buf,
VARDATA_ANY(sstate), VARSIZE_ANY_EXHDR(sstate));
result = makePolyNumAggStateCurrentContext(false);
/* N */
result->N = pq_getmsgint64(&buf);
/* sumX */
temp = DirectFunctionCall3(numeric_recv,
PointerGetDatum(&buf),
ObjectIdGetDatum(InvalidOid),
Int32GetDatum(-1));
init_var_from_num(DatumGetNumeric(temp), &num);
#ifdef HAVE_INT128
numericvar_to_int128(&num, &result->sumX);
#else
accum_sum_add(&result->sumX, &num);
#endif
pq_getmsgend(&buf);
pfree(buf.data);
PG_RETURN_POINTER(result);
}
/*
* Inverse transition functions to go with the above.
*/
Datum
int2_accum_inv(PG_FUNCTION_ARGS)
{
PolyNumAggState *state;
state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0);
/* Should not get here with no state */
if (state == NULL)
elog(ERROR, "int2_accum_inv called with NULL state");
if (!PG_ARGISNULL(1))
{
#ifdef HAVE_INT128
do_int128_discard(state, (int128) PG_GETARG_INT16(1));
#else
Numeric newval;
newval = DatumGetNumeric(DirectFunctionCall1(int2_numeric,
PG_GETARG_DATUM(1)));
/* Should never fail, all inputs have dscale 0 */
if (!do_numeric_discard(state, newval))
elog(ERROR, "do_numeric_discard failed unexpectedly");
#endif
}
PG_RETURN_POINTER(state);
}
Datum
int4_accum_inv(PG_FUNCTION_ARGS)
{
PolyNumAggState *state;
state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0);
/* Should not get here with no state */
if (state == NULL)
elog(ERROR, "int4_accum_inv called with NULL state");
if (!PG_ARGISNULL(1))
{
#ifdef HAVE_INT128
do_int128_discard(state, (int128) PG_GETARG_INT32(1));
#else
Numeric newval;
newval = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
PG_GETARG_DATUM(1)));
/* Should never fail, all inputs have dscale 0 */
if (!do_numeric_discard(state, newval))
elog(ERROR, "do_numeric_discard failed unexpectedly");
#endif
}
PG_RETURN_POINTER(state);
}
Datum
int8_accum_inv(PG_FUNCTION_ARGS)
{
NumericAggState *state;
state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
/* Should not get here with no state */
if (state == NULL)
elog(ERROR, "int8_accum_inv called with NULL state");
if (!PG_ARGISNULL(1))
{
Numeric newval;
newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
PG_GETARG_DATUM(1)));
/* Should never fail, all inputs have dscale 0 */
if (!do_numeric_discard(state, newval))
elog(ERROR, "do_numeric_discard failed unexpectedly");
}
PG_RETURN_POINTER(state);
}
Datum
int8_avg_accum_inv(PG_FUNCTION_ARGS)
{
PolyNumAggState *state;
state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0);
/* Should not get here with no state */
if (state == NULL)
elog(ERROR, "int8_avg_accum_inv called with NULL state");
if (!PG_ARGISNULL(1))
{
#ifdef HAVE_INT128
do_int128_discard(state, (int128) PG_GETARG_INT64(1));
#else
Numeric newval;
newval = DatumGetNumeric(DirectFunctionCall1(int8_numeric,
PG_GETARG_DATUM(1)));
/* Should never fail, all inputs have dscale 0 */
if (!do_numeric_discard(state, newval))
elog(ERROR, "do_numeric_discard failed unexpectedly");
#endif
}
PG_RETURN_POINTER(state);
}
Datum
numeric_poly_sum(PG_FUNCTION_ARGS)
{
#ifdef HAVE_INT128
2015-05-24 03:35:49 +02:00
PolyNumAggState *state;
Numeric res;
NumericVar result;
state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0);
/* If there were no non-null inputs, return NULL */
if (state == NULL || state->N == 0)
PG_RETURN_NULL();
init_var(&result);
int128_to_numericvar(state->sumX, &result);
res = make_result(&result);
free_var(&result);
PG_RETURN_NUMERIC(res);
#else
return numeric_sum(fcinfo);
#endif
}
Datum
numeric_poly_avg(PG_FUNCTION_ARGS)
{
#ifdef HAVE_INT128
2015-05-24 03:35:49 +02:00
PolyNumAggState *state;
NumericVar result;
Datum countd,
sumd;
state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0);
/* If there were no non-null inputs, return NULL */
if (state == NULL || state->N == 0)
PG_RETURN_NULL();
init_var(&result);
int128_to_numericvar(state->sumX, &result);
countd = DirectFunctionCall1(int8_numeric,
Int64GetDatumFast(state->N));
sumd = NumericGetDatum(make_result(&result));
free_var(&result);
PG_RETURN_DATUM(DirectFunctionCall2(numeric_div, sumd, countd));
#else
return numeric_avg(fcinfo);
#endif
}
Datum
numeric_avg(PG_FUNCTION_ARGS)
{
NumericAggState *state;
Datum N_datum;
Datum sumX_datum;
NumericVar sumX_var;
state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
/* If there were no non-null inputs, return NULL */
if (state == NULL || (state->N + state->NaNcount) == 0)
PG_RETURN_NULL();
if (state->NaNcount > 0) /* there was at least one NaN input */
PG_RETURN_NUMERIC(make_result(&const_nan));
N_datum = DirectFunctionCall1(int8_numeric, Int64GetDatum(state->N));
init_var(&sumX_var);
accum_sum_final(&state->sumX, &sumX_var);
sumX_datum = NumericGetDatum(make_result(&sumX_var));
free_var(&sumX_var);
PG_RETURN_DATUM(DirectFunctionCall2(numeric_div, sumX_datum, N_datum));
}
Datum
numeric_sum(PG_FUNCTION_ARGS)
{
NumericAggState *state;
NumericVar sumX_var;
Numeric result;
state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
/* If there were no non-null inputs, return NULL */
if (state == NULL || (state->N + state->NaNcount) == 0)
PG_RETURN_NULL();
if (state->NaNcount > 0) /* there was at least one NaN input */
PG_RETURN_NUMERIC(make_result(&const_nan));
init_var(&sumX_var);
accum_sum_final(&state->sumX, &sumX_var);
result = make_result(&sumX_var);
free_var(&sumX_var);
PG_RETURN_NUMERIC(result);
}
/*
* Workhorse routine for the standard deviance and variance
* aggregates. 'state' is aggregate's transition state.
* 'variance' specifies whether we should calculate the
* variance or the standard deviation. 'sample' indicates whether the
* caller is interested in the sample or the population
* variance/stddev.
*
* If appropriate variance statistic is undefined for the input,
* *is_null is set to true and NULL is returned.
*/
static Numeric
numeric_stddev_internal(NumericAggState *state,
bool variance, bool sample,
bool *is_null)
{
Numeric res;
NumericVar vN,
vsumX,
vsumX2,
vNminus1;
const NumericVar *comp;
int rscale;
/* Deal with empty input and NaN-input cases */
if (state == NULL || (state->N + state->NaNcount) == 0)
{
*is_null = true;
return NULL;
}
*is_null = false;
if (state->NaNcount > 0)
return make_result(&const_nan);
init_var(&vN);
init_var(&vsumX);
init_var(&vsumX2);
int64_to_numericvar(state->N, &vN);
accum_sum_final(&(state->sumX), &vsumX);
accum_sum_final(&(state->sumX2), &vsumX2);
/*
2006-10-04 02:30:14 +02:00
* Sample stddev and variance are undefined when N <= 1; population stddev
* is undefined when N == 0. Return NULL in either case.
*/
if (sample)
comp = &const_one;
else
comp = &const_zero;
if (cmp_var(&vN, comp) <= 0)
{
*is_null = true;
return NULL;
}
init_var(&vNminus1);
sub_var(&vN, &const_one, &vNminus1);
/* compute rscale for mul_var calls */
rscale = vsumX.dscale * 2;
mul_var(&vsumX, &vsumX, &vsumX, rscale); /* vsumX = sumX * sumX */
Phase 2 of pgindent updates. Change pg_bsd_indent to follow upstream rules for placement of comments to the right of code, and remove pgindent hack that caused comments following #endif to not obey the general rule. Commit e3860ffa4dd0dad0dd9eea4be9cc1412373a8c89 wasn't actually using the published version of pg_bsd_indent, but a hacked-up version that tried to minimize the amount of movement of comments to the right of code. The situation of interest is where such a comment has to be moved to the right of its default placement at column 33 because there's code there. BSD indent has always moved right in units of tab stops in such cases --- but in the previous incarnation, indent was working in 8-space tab stops, while now it knows we use 4-space tabs. So the net result is that in about half the cases, such comments are placed one tab stop left of before. This is better all around: it leaves more room on the line for comment text, and it means that in such cases the comment uniformly starts at the next 4-space tab stop after the code, rather than sometimes one and sometimes two tabs after. Also, ensure that comments following #endif are indented the same as comments following other preprocessor commands such as #else. That inconsistency turns out to have been self-inflicted damage from a poorly-thought-through post-indent "fixup" in pgindent. This patch is much less interesting than the first round of indent changes, but also bulkier, so I thought it best to separate the effects. Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:18:54 +02:00
mul_var(&vN, &vsumX2, &vsumX2, rscale); /* vsumX2 = N * sumX2 */
2006-10-04 02:30:14 +02:00
sub_var(&vsumX2, &vsumX, &vsumX2); /* N * sumX2 - sumX * sumX */
if (cmp_var(&vsumX2, &const_zero) <= 0)
{
/* Watch out for roundoff error producing a negative numerator */
res = make_result(&const_zero);
}
else
{
if (sample)
Phase 2 of pgindent updates. Change pg_bsd_indent to follow upstream rules for placement of comments to the right of code, and remove pgindent hack that caused comments following #endif to not obey the general rule. Commit e3860ffa4dd0dad0dd9eea4be9cc1412373a8c89 wasn't actually using the published version of pg_bsd_indent, but a hacked-up version that tried to minimize the amount of movement of comments to the right of code. The situation of interest is where such a comment has to be moved to the right of its default placement at column 33 because there's code there. BSD indent has always moved right in units of tab stops in such cases --- but in the previous incarnation, indent was working in 8-space tab stops, while now it knows we use 4-space tabs. So the net result is that in about half the cases, such comments are placed one tab stop left of before. This is better all around: it leaves more room on the line for comment text, and it means that in such cases the comment uniformly starts at the next 4-space tab stop after the code, rather than sometimes one and sometimes two tabs after. Also, ensure that comments following #endif are indented the same as comments following other preprocessor commands such as #else. That inconsistency turns out to have been self-inflicted damage from a poorly-thought-through post-indent "fixup" in pgindent. This patch is much less interesting than the first round of indent changes, but also bulkier, so I thought it best to separate the effects. Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:18:54 +02:00
mul_var(&vN, &vNminus1, &vNminus1, 0); /* N * (N - 1) */
else
2007-11-15 22:14:46 +01:00
mul_var(&vN, &vN, &vNminus1, 0); /* N * N */
rscale = select_div_scale(&vsumX2, &vNminus1);
Phase 2 of pgindent updates. Change pg_bsd_indent to follow upstream rules for placement of comments to the right of code, and remove pgindent hack that caused comments following #endif to not obey the general rule. Commit e3860ffa4dd0dad0dd9eea4be9cc1412373a8c89 wasn't actually using the published version of pg_bsd_indent, but a hacked-up version that tried to minimize the amount of movement of comments to the right of code. The situation of interest is where such a comment has to be moved to the right of its default placement at column 33 because there's code there. BSD indent has always moved right in units of tab stops in such cases --- but in the previous incarnation, indent was working in 8-space tab stops, while now it knows we use 4-space tabs. So the net result is that in about half the cases, such comments are placed one tab stop left of before. This is better all around: it leaves more room on the line for comment text, and it means that in such cases the comment uniformly starts at the next 4-space tab stop after the code, rather than sometimes one and sometimes two tabs after. Also, ensure that comments following #endif are indented the same as comments following other preprocessor commands such as #else. That inconsistency turns out to have been self-inflicted damage from a poorly-thought-through post-indent "fixup" in pgindent. This patch is much less interesting than the first round of indent changes, but also bulkier, so I thought it best to separate the effects. Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:18:54 +02:00
div_var(&vsumX2, &vNminus1, &vsumX, rscale, true); /* variance */
if (!variance)
2006-10-04 02:30:14 +02:00
sqrt_var(&vsumX, &vsumX, rscale); /* stddev */
res = make_result(&vsumX);
}
free_var(&vNminus1);
free_var(&vsumX);
free_var(&vsumX2);
return res;
}
Datum
numeric_var_samp(PG_FUNCTION_ARGS)
{
NumericAggState *state;
2006-10-04 02:30:14 +02:00
Numeric res;
bool is_null;
state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
res = numeric_stddev_internal(state, true, true, &is_null);
if (is_null)
PG_RETURN_NULL();
else
PG_RETURN_NUMERIC(res);
}
Datum
numeric_stddev_samp(PG_FUNCTION_ARGS)
{
NumericAggState *state;
2006-10-04 02:30:14 +02:00
Numeric res;
bool is_null;
state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
res = numeric_stddev_internal(state, false, true, &is_null);
if (is_null)
PG_RETURN_NULL();
else
PG_RETURN_NUMERIC(res);
}
Datum
numeric_var_pop(PG_FUNCTION_ARGS)
{
NumericAggState *state;
2006-10-04 02:30:14 +02:00
Numeric res;
bool is_null;
state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
res = numeric_stddev_internal(state, true, false, &is_null);
if (is_null)
PG_RETURN_NULL();
else
PG_RETURN_NUMERIC(res);
}
Datum
numeric_stddev_pop(PG_FUNCTION_ARGS)
{
NumericAggState *state;
2006-10-04 02:30:14 +02:00
Numeric res;
bool is_null;
state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
res = numeric_stddev_internal(state, false, false, &is_null);
if (is_null)
PG_RETURN_NULL();
else
PG_RETURN_NUMERIC(res);
}
#ifdef HAVE_INT128
static Numeric
numeric_poly_stddev_internal(Int128AggState *state,
2015-05-24 03:35:49 +02:00
bool variance, bool sample,
bool *is_null)
{
NumericAggState numstate;
Numeric res;
/* Initialize an empty agg state */
memset(&numstate, 0, sizeof(NumericAggState));
if (state)
{
NumericVar tmp_var;
numstate.N = state->N;
init_var(&tmp_var);
int128_to_numericvar(state->sumX, &tmp_var);
accum_sum_add(&numstate.sumX, &tmp_var);
int128_to_numericvar(state->sumX2, &tmp_var);
accum_sum_add(&numstate.sumX2, &tmp_var);
free_var(&tmp_var);
}
res = numeric_stddev_internal(&numstate, variance, sample, is_null);
if (numstate.sumX.ndigits > 0)
{
pfree(numstate.sumX.pos_digits);
pfree(numstate.sumX.neg_digits);
}
if (numstate.sumX2.ndigits > 0)
{
pfree(numstate.sumX2.pos_digits);
pfree(numstate.sumX2.neg_digits);
}
return res;
}
#endif
Datum
numeric_poly_var_samp(PG_FUNCTION_ARGS)
{
#ifdef HAVE_INT128
2015-05-24 03:35:49 +02:00
PolyNumAggState *state;
Numeric res;
bool is_null;
state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0);
res = numeric_poly_stddev_internal(state, true, true, &is_null);
if (is_null)
PG_RETURN_NULL();
else
PG_RETURN_NUMERIC(res);
#else
return numeric_var_samp(fcinfo);
#endif
}
Datum
numeric_poly_stddev_samp(PG_FUNCTION_ARGS)
{
#ifdef HAVE_INT128
2015-05-24 03:35:49 +02:00
PolyNumAggState *state;
Numeric res;
bool is_null;
state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0);
res = numeric_poly_stddev_internal(state, false, true, &is_null);
if (is_null)
PG_RETURN_NULL();
else
PG_RETURN_NUMERIC(res);
#else
return numeric_stddev_samp(fcinfo);
#endif
}
Datum
numeric_poly_var_pop(PG_FUNCTION_ARGS)
{
#ifdef HAVE_INT128
2015-05-24 03:35:49 +02:00
PolyNumAggState *state;
Numeric res;
bool is_null;
state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0);
res = numeric_poly_stddev_internal(state, true, false, &is_null);
if (is_null)
PG_RETURN_NULL();
else
PG_RETURN_NUMERIC(res);
#else
return numeric_var_pop(fcinfo);
#endif
}
Datum
numeric_poly_stddev_pop(PG_FUNCTION_ARGS)
{
#ifdef HAVE_INT128
2015-05-24 03:35:49 +02:00
PolyNumAggState *state;
Numeric res;
bool is_null;
state = PG_ARGISNULL(0) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(0);
res = numeric_poly_stddev_internal(state, false, false, &is_null);
if (is_null)
PG_RETURN_NULL();
else
PG_RETURN_NUMERIC(res);
#else
return numeric_stddev_pop(fcinfo);
#endif
}
/*
* SUM transition functions for integer datatypes.
*
* To avoid overflow, we use accumulators wider than the input datatype.
* A Numeric accumulator is needed for int8 input; for int4 and int2
* inputs, we use int8 accumulators which should be sufficient for practical
* purposes. (The latter two therefore don't really belong in this file,
* but we keep them here anyway.)
*
* Because SQL defines the SUM() of no values to be NULL, not zero,
* the initial condition of the transition data value needs to be NULL. This
* means we can't rely on ExecAgg to automatically insert the first non-null
* data value into the transition data: it doesn't know how to do the type
* conversion. The upshot is that these routines have to be marked non-strict
* and handle substitution of the first non-null input themselves.
*
* Note: these functions are used only in plain aggregation mode.
* In moving-aggregate mode, we use intX_avg_accum and intX_avg_accum_inv.
*/
Datum
int2_sum(PG_FUNCTION_ARGS)
{
int64 newval;
if (PG_ARGISNULL(0))
{
/* No non-null input seen so far... */
if (PG_ARGISNULL(1))
PG_RETURN_NULL(); /* still no non-null */
/* This is the first non-null input. */
newval = (int64) PG_GETARG_INT16(1);
PG_RETURN_INT64(newval);
}
/*
* If we're invoked as an aggregate, we can cheat and modify our first
2005-10-15 04:49:52 +02:00
* parameter in-place to avoid palloc overhead. If not, we need to return
* the new value of the transition variable. (If int8 is pass-by-value,
* then of course this is useless as well as incorrect, so just ifdef it
* out.)
*/
#ifndef USE_FLOAT8_BYVAL /* controls int8 too */
if (AggCheckCallContext(fcinfo, NULL))
{
2005-10-15 04:49:52 +02:00
int64 *oldsum = (int64 *) PG_GETARG_POINTER(0);
/* Leave the running sum unchanged in the new input is null */
if (!PG_ARGISNULL(1))
*oldsum = *oldsum + (int64) PG_GETARG_INT16(1);
PG_RETURN_POINTER(oldsum);
}
else
#endif
{
int64 oldsum = PG_GETARG_INT64(0);
/* Leave sum unchanged if new input is null. */
if (PG_ARGISNULL(1))
PG_RETURN_INT64(oldsum);
/* OK to do the addition. */
newval = oldsum + (int64) PG_GETARG_INT16(1);
PG_RETURN_INT64(newval);
}
}
Datum
int4_sum(PG_FUNCTION_ARGS)
{
int64 newval;
if (PG_ARGISNULL(0))
{
/* No non-null input seen so far... */
if (PG_ARGISNULL(1))
PG_RETURN_NULL(); /* still no non-null */
/* This is the first non-null input. */
newval = (int64) PG_GETARG_INT32(1);
PG_RETURN_INT64(newval);
}
/*
* If we're invoked as an aggregate, we can cheat and modify our first
2005-10-15 04:49:52 +02:00
* parameter in-place to avoid palloc overhead. If not, we need to return
* the new value of the transition variable. (If int8 is pass-by-value,
* then of course this is useless as well as incorrect, so just ifdef it
* out.)
*/
#ifndef USE_FLOAT8_BYVAL /* controls int8 too */
if (AggCheckCallContext(fcinfo, NULL))
{
2005-10-15 04:49:52 +02:00
int64 *oldsum = (int64 *) PG_GETARG_POINTER(0);
/* Leave the running sum unchanged in the new input is null */
if (!PG_ARGISNULL(1))
*oldsum = *oldsum + (int64) PG_GETARG_INT32(1);
PG_RETURN_POINTER(oldsum);
}
else
#endif
{
int64 oldsum = PG_GETARG_INT64(0);
/* Leave sum unchanged if new input is null. */
if (PG_ARGISNULL(1))
PG_RETURN_INT64(oldsum);
/* OK to do the addition. */
newval = oldsum + (int64) PG_GETARG_INT32(1);
PG_RETURN_INT64(newval);
}
}
/*
* Note: this function is obsolete, it's no longer used for SUM(int8).
*/
Datum
int8_sum(PG_FUNCTION_ARGS)
{
Numeric oldsum;
Datum newval;
if (PG_ARGISNULL(0))
{
/* No non-null input seen so far... */
if (PG_ARGISNULL(1))
PG_RETURN_NULL(); /* still no non-null */
/* This is the first non-null input. */
newval = DirectFunctionCall1(int8_numeric, PG_GETARG_DATUM(1));
PG_RETURN_DATUM(newval);
}
/*
* Note that we cannot special-case the aggregate case here, as we do for
2005-10-15 04:49:52 +02:00
* int2_sum and int4_sum: numeric is of variable size, so we cannot modify
* our first parameter in-place.
*/
oldsum = PG_GETARG_NUMERIC(0);
/* Leave sum unchanged if new input is null. */
if (PG_ARGISNULL(1))
PG_RETURN_NUMERIC(oldsum);
/* OK to do the addition. */
newval = DirectFunctionCall1(int8_numeric, PG_GETARG_DATUM(1));
PG_RETURN_DATUM(DirectFunctionCall2(numeric_add,
NumericGetDatum(oldsum), newval));
}
/*
* Routines for avg(int2) and avg(int4). The transition datatype
* is a two-element int8 array, holding count and sum.
*
* These functions are also used for sum(int2) and sum(int4) when
* operating in moving-aggregate mode, since for correct inverse transitions
* we need to count the inputs.
*/
typedef struct Int8TransTypeData
{
int64 count;
int64 sum;
} Int8TransTypeData;
Datum
int2_avg_accum(PG_FUNCTION_ARGS)
{
ArrayType *transarray;
int16 newval = PG_GETARG_INT16(1);
Int8TransTypeData *transdata;
/*
* If we're invoked as an aggregate, we can cheat and modify our first
2005-10-15 04:49:52 +02:00
* parameter in-place to reduce palloc overhead. Otherwise we need to make
* a copy of it before scribbling on it.
*/
if (AggCheckCallContext(fcinfo, NULL))
transarray = PG_GETARG_ARRAYTYPE_P(0);
else
transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
if (ARR_HASNULL(transarray) ||
ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
elog(ERROR, "expected 2-element int8 array");
transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
transdata->count++;
transdata->sum += newval;
PG_RETURN_ARRAYTYPE_P(transarray);
}
Datum
int4_avg_accum(PG_FUNCTION_ARGS)
{
ArrayType *transarray;
int32 newval = PG_GETARG_INT32(1);
Int8TransTypeData *transdata;
/*
* If we're invoked as an aggregate, we can cheat and modify our first
2005-10-15 04:49:52 +02:00
* parameter in-place to reduce palloc overhead. Otherwise we need to make
* a copy of it before scribbling on it.
*/
if (AggCheckCallContext(fcinfo, NULL))
transarray = PG_GETARG_ARRAYTYPE_P(0);
else
transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
if (ARR_HASNULL(transarray) ||
ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
elog(ERROR, "expected 2-element int8 array");
transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
transdata->count++;
transdata->sum += newval;
PG_RETURN_ARRAYTYPE_P(transarray);
}
Datum
int4_avg_combine(PG_FUNCTION_ARGS)
{
ArrayType *transarray1;
ArrayType *transarray2;
Int8TransTypeData *state1;
Int8TransTypeData *state2;
if (!AggCheckCallContext(fcinfo, NULL))
elog(ERROR, "aggregate function called in non-aggregate context");
transarray1 = PG_GETARG_ARRAYTYPE_P(0);
transarray2 = PG_GETARG_ARRAYTYPE_P(1);
if (ARR_HASNULL(transarray1) ||
ARR_SIZE(transarray1) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
elog(ERROR, "expected 2-element int8 array");
if (ARR_HASNULL(transarray2) ||
ARR_SIZE(transarray2) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
elog(ERROR, "expected 2-element int8 array");
state1 = (Int8TransTypeData *) ARR_DATA_PTR(transarray1);
state2 = (Int8TransTypeData *) ARR_DATA_PTR(transarray2);
state1->count += state2->count;
state1->sum += state2->sum;
PG_RETURN_ARRAYTYPE_P(transarray1);
}
Datum
int2_avg_accum_inv(PG_FUNCTION_ARGS)
{
ArrayType *transarray;
int16 newval = PG_GETARG_INT16(1);
Int8TransTypeData *transdata;
/*
* If we're invoked as an aggregate, we can cheat and modify our first
* parameter in-place to reduce palloc overhead. Otherwise we need to make
* a copy of it before scribbling on it.
*/
if (AggCheckCallContext(fcinfo, NULL))
transarray = PG_GETARG_ARRAYTYPE_P(0);
else
transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
if (ARR_HASNULL(transarray) ||
ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
elog(ERROR, "expected 2-element int8 array");
transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
transdata->count--;
transdata->sum -= newval;
PG_RETURN_ARRAYTYPE_P(transarray);
}
Datum
int4_avg_accum_inv(PG_FUNCTION_ARGS)
{
ArrayType *transarray;
int32 newval = PG_GETARG_INT32(1);
Int8TransTypeData *transdata;
/*
* If we're invoked as an aggregate, we can cheat and modify our first
* parameter in-place to reduce palloc overhead. Otherwise we need to make
* a copy of it before scribbling on it.
*/
if (AggCheckCallContext(fcinfo, NULL))
transarray = PG_GETARG_ARRAYTYPE_P(0);
else
transarray = PG_GETARG_ARRAYTYPE_P_COPY(0);
if (ARR_HASNULL(transarray) ||
ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
elog(ERROR, "expected 2-element int8 array");
transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
transdata->count--;
transdata->sum -= newval;
PG_RETURN_ARRAYTYPE_P(transarray);
}
Datum
int8_avg(PG_FUNCTION_ARGS)
{
ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
Int8TransTypeData *transdata;
Datum countd,
sumd;
if (ARR_HASNULL(transarray) ||
ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
elog(ERROR, "expected 2-element int8 array");
transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
/* SQL defines AVG of no values to be NULL */
if (transdata->count == 0)
PG_RETURN_NULL();
countd = DirectFunctionCall1(int8_numeric,
Int64GetDatumFast(transdata->count));
sumd = DirectFunctionCall1(int8_numeric,
Int64GetDatumFast(transdata->sum));
PG_RETURN_DATUM(DirectFunctionCall2(numeric_div, sumd, countd));
}
/*
* SUM(int2) and SUM(int4) both return int8, so we can use this
* final function for both.
*/
Datum
int2int4_sum(PG_FUNCTION_ARGS)
{
ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
Int8TransTypeData *transdata;
if (ARR_HASNULL(transarray) ||
ARR_SIZE(transarray) != ARR_OVERHEAD_NONULLS(1) + sizeof(Int8TransTypeData))
elog(ERROR, "expected 2-element int8 array");
transdata = (Int8TransTypeData *) ARR_DATA_PTR(transarray);
/* SQL defines SUM of no values to be NULL */
if (transdata->count == 0)
PG_RETURN_NULL();
PG_RETURN_DATUM(Int64GetDatumFast(transdata->sum));
}
/* ----------------------------------------------------------------------
*
* Debug support
*
* ----------------------------------------------------------------------
*/
#ifdef NUMERIC_DEBUG
/*
* dump_numeric() - Dump a value in the db storage format for debugging
*/
static void
dump_numeric(const char *str, Numeric num)
{
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
NumericDigit *digits = NUMERIC_DIGITS(num);
int ndigits;
1999-05-25 18:15:34 +02:00
int i;
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
ndigits = NUMERIC_NDIGITS(num);
printf("%s: NUMERIC w=%d d=%d ", str,
NUMERIC_WEIGHT(num), NUMERIC_DSCALE(num));
switch (NUMERIC_SIGN(num))
{
1999-05-25 18:15:34 +02:00
case NUMERIC_POS:
printf("POS");
break;
case NUMERIC_NEG:
printf("NEG");
break;
case NUMERIC_NAN:
printf("NaN");
break;
default:
printf("SIGN=0x%x", NUMERIC_SIGN(num));
break;
}
for (i = 0; i < ndigits; i++)
printf(" %0*d", DEC_DIGITS, digits[i]);
printf("\n");
}
/*
* dump_var() - Dump a value in the variable format for debugging
*/
static void
dump_var(const char *str, NumericVar *var)
{
1999-05-25 18:15:34 +02:00
int i;
printf("%s: VAR w=%d d=%d ", str, var->weight, var->dscale);
switch (var->sign)
{
1999-05-25 18:15:34 +02:00
case NUMERIC_POS:
printf("POS");
break;
case NUMERIC_NEG:
printf("NEG");
break;
case NUMERIC_NAN:
printf("NaN");
break;
default:
printf("SIGN=0x%x", var->sign);
break;
}
for (i = 0; i < var->ndigits; i++)
printf(" %0*d", DEC_DIGITS, var->digits[i]);
1999-05-25 18:15:34 +02:00
printf("\n");
}
Phase 2 of pgindent updates. Change pg_bsd_indent to follow upstream rules for placement of comments to the right of code, and remove pgindent hack that caused comments following #endif to not obey the general rule. Commit e3860ffa4dd0dad0dd9eea4be9cc1412373a8c89 wasn't actually using the published version of pg_bsd_indent, but a hacked-up version that tried to minimize the amount of movement of comments to the right of code. The situation of interest is where such a comment has to be moved to the right of its default placement at column 33 because there's code there. BSD indent has always moved right in units of tab stops in such cases --- but in the previous incarnation, indent was working in 8-space tab stops, while now it knows we use 4-space tabs. So the net result is that in about half the cases, such comments are placed one tab stop left of before. This is better all around: it leaves more room on the line for comment text, and it means that in such cases the comment uniformly starts at the next 4-space tab stop after the code, rather than sometimes one and sometimes two tabs after. Also, ensure that comments following #endif are indented the same as comments following other preprocessor commands such as #else. That inconsistency turns out to have been self-inflicted damage from a poorly-thought-through post-indent "fixup" in pgindent. This patch is much less interesting than the first round of indent changes, but also bulkier, so I thought it best to separate the effects. Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:18:54 +02:00
#endif /* NUMERIC_DEBUG */
/* ----------------------------------------------------------------------
*
* Local functions follow
*
* In general, these do not support NaNs --- callers must eliminate
* the possibility of NaN first. (make_result() is an exception.)
*
* ----------------------------------------------------------------------
*/
/*
* alloc_var() -
*
* Allocate a digit buffer of ndigits digits (plus a spare digit for rounding)
*/
static void
alloc_var(NumericVar *var, int ndigits)
{
digitbuf_free(var->buf);
var->buf = digitbuf_alloc(ndigits + 1);
2003-08-04 02:43:34 +02:00
var->buf[0] = 0; /* spare digit for rounding */
var->digits = var->buf + 1;
var->ndigits = ndigits;
}
/*
* free_var() -
*
* Return the digit buffer of a variable to the free pool
*/
static void
1999-05-26 00:43:53 +02:00
free_var(NumericVar *var)
{
digitbuf_free(var->buf);
var->buf = NULL;
var->digits = NULL;
var->sign = NUMERIC_NAN;
}
/*
* zero_var() -
*
* Set a variable to ZERO.
* Note: its dscale is not touched.
*/
static void
zero_var(NumericVar *var)
{
digitbuf_free(var->buf);
var->buf = NULL;
var->digits = NULL;
var->ndigits = 0;
var->weight = 0; /* by convention; doesn't really matter */
var->sign = NUMERIC_POS; /* anything but NAN... */
}
/*
* set_var_from_str()
*
* Parse a string and put the number into a variable
*
* This function does not handle leading or trailing spaces, and it doesn't
* accept "NaN" either. It returns the end+1 position so that caller can
* check for trailing spaces/garbage if deemed necessary.
*
* cp is the place to actually start parsing; str is what to use in error
* reports. (Typically cp would be the same except advanced over spaces.)
*/
static const char *
set_var_from_str(const char *str, const char *cp, NumericVar *dest)
{
bool have_dp = false;
int i;
unsigned char *decdigits;
int sign = NUMERIC_POS;
int dweight = -1;
int ddigits;
int dscale = 0;
int weight;
int ndigits;
int offset;
NumericDigit *digits;
/*
2005-10-15 04:49:52 +02:00
* We first parse the string to extract decimal digits and determine the
* correct decimal weight. Then convert to NBASE representation.
*/
switch (*cp)
{
1999-05-25 18:15:34 +02:00
case '+':
sign = NUMERIC_POS;
1999-05-25 18:15:34 +02:00
cp++;
break;
1999-05-25 18:15:34 +02:00
case '-':
sign = NUMERIC_NEG;
1999-05-25 18:15:34 +02:00
cp++;
break;
}
if (*cp == '.')
{
have_dp = true;
cp++;
}
if (!isdigit((unsigned char) *cp))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
"numeric", str)));
2003-08-04 02:43:34 +02:00
decdigits = (unsigned char *) palloc(strlen(cp) + DEC_DIGITS * 2);
/* leading padding for digit alignment later */
memset(decdigits, 0, DEC_DIGITS);
i = DEC_DIGITS;
while (*cp)
{
if (isdigit((unsigned char) *cp))
{
decdigits[i++] = *cp++ - '0';
if (!have_dp)
dweight++;
else
dscale++;
}
else if (*cp == '.')
{
if (have_dp)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
"numeric", str)));
have_dp = true;
cp++;
}
else
break;
}
ddigits = i - DEC_DIGITS;
/* trailing padding for digit alignment later */
2003-08-04 02:43:34 +02:00
memset(decdigits + i, 0, DEC_DIGITS - 1);
/* Handle exponent, if any */
if (*cp == 'e' || *cp == 'E')
{
long exponent;
char *endptr;
cp++;
exponent = strtol(cp, &endptr, 10);
if (endptr == cp)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
"numeric", str)));
cp = endptr;
Remove bogus dependencies on NUMERIC_MAX_PRECISION. NUMERIC_MAX_PRECISION is a purely arbitrary constraint on the precision and scale you can write in a numeric typmod. It might once have had something to do with the allowed range of a typmod-less numeric value, but at least since 9.1 we've allowed, and documented that we allowed, any value that would physically fit in the numeric storage format; which is something over 100000 decimal digits, not 1000. Hence, get rid of numeric_in()'s use of NUMERIC_MAX_PRECISION as a limit on the allowed range of the exponent in scientific-format input. That was especially silly in view of the fact that you can enter larger numbers as long as you don't use 'e' to do it. Just constrain the value enough to avoid localized overflow, and let make_result be the final arbiter of what is too large. Likewise adjust ecpg's equivalent of this code. Also get rid of numeric_recv()'s use of NUMERIC_MAX_PRECISION to limit the number of base-NBASE digits it would accept. That created a dump/restore hazard for binary COPY without doing anything useful; the wire-format limit on number of digits (65535) is about as tight as we would want. In HEAD, also get rid of pg_size_bytes()'s unnecessary intimacy with what the numeric range limit is. That code doesn't exist in the back branches. Per gripe from Aravind Kumar. Back-patch to all supported branches, since they all contain the documentation claim about allowed range of NUMERIC (cf commit cabf5d84b). Discussion: <2895.1471195721@sss.pgh.pa.us>
2016-08-14 21:06:01 +02:00
/*
* At this point, dweight and dscale can't be more than about
* INT_MAX/2 due to the MaxAllocSize limit on string length, so
* constraining the exponent similarly should be enough to prevent
* integer overflow in this function. If the value is too large to
* fit in storage format, make_result() will complain about it later;
* for consistency use the same ereport errcode/text as make_result().
*/
if (exponent >= INT_MAX / 2 || exponent <= -(INT_MAX / 2))
ereport(ERROR,
Remove bogus dependencies on NUMERIC_MAX_PRECISION. NUMERIC_MAX_PRECISION is a purely arbitrary constraint on the precision and scale you can write in a numeric typmod. It might once have had something to do with the allowed range of a typmod-less numeric value, but at least since 9.1 we've allowed, and documented that we allowed, any value that would physically fit in the numeric storage format; which is something over 100000 decimal digits, not 1000. Hence, get rid of numeric_in()'s use of NUMERIC_MAX_PRECISION as a limit on the allowed range of the exponent in scientific-format input. That was especially silly in view of the fact that you can enter larger numbers as long as you don't use 'e' to do it. Just constrain the value enough to avoid localized overflow, and let make_result be the final arbiter of what is too large. Likewise adjust ecpg's equivalent of this code. Also get rid of numeric_recv()'s use of NUMERIC_MAX_PRECISION to limit the number of base-NBASE digits it would accept. That created a dump/restore hazard for binary COPY without doing anything useful; the wire-format limit on number of digits (65535) is about as tight as we would want. In HEAD, also get rid of pg_size_bytes()'s unnecessary intimacy with what the numeric range limit is. That code doesn't exist in the back branches. Per gripe from Aravind Kumar. Back-patch to all supported branches, since they all contain the documentation claim about allowed range of NUMERIC (cf commit cabf5d84b). Discussion: <2895.1471195721@sss.pgh.pa.us>
2016-08-14 21:06:01 +02:00
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value overflows numeric format")));
dweight += (int) exponent;
dscale -= (int) exponent;
if (dscale < 0)
dscale = 0;
}
/*
2005-10-15 04:49:52 +02:00
* Okay, convert pure-decimal representation to base NBASE. First we need
* to determine the converted weight and ndigits. offset is the number of
* decimal zeroes to insert before the first given digit to have a
* correctly aligned first NBASE digit.
*/
if (dweight >= 0)
2003-08-04 02:43:34 +02:00
weight = (dweight + 1 + DEC_DIGITS - 1) / DEC_DIGITS - 1;
else
2003-08-04 02:43:34 +02:00
weight = -((-dweight - 1) / DEC_DIGITS + 1);
offset = (weight + 1) * DEC_DIGITS - (dweight + 1);
2003-08-04 02:43:34 +02:00
ndigits = (ddigits + offset + DEC_DIGITS - 1) / DEC_DIGITS;
alloc_var(dest, ndigits);
dest->sign = sign;
dest->weight = weight;
dest->dscale = dscale;
i = DEC_DIGITS - offset;
digits = dest->digits;
while (ndigits-- > 0)
{
#if DEC_DIGITS == 4
2003-08-04 02:43:34 +02:00
*digits++ = ((decdigits[i] * 10 + decdigits[i + 1]) * 10 +
decdigits[i + 2]) * 10 + decdigits[i + 3];
#elif DEC_DIGITS == 2
2003-08-04 02:43:34 +02:00
*digits++ = decdigits[i] * 10 + decdigits[i + 1];
#elif DEC_DIGITS == 1
*digits++ = decdigits[i];
#else
#error unsupported NBASE
#endif
i += DEC_DIGITS;
}
pfree(decdigits);
/* Strip any leading/trailing zeroes, and normalize weight if zero */
strip_var(dest);
/* Return end+1 position for caller */
return cp;
}
/*
* set_var_from_num() -
*
* Convert the packed db format into a variable
*/
static void
1999-05-26 00:43:53 +02:00
set_var_from_num(Numeric num, NumericVar *dest)
{
int ndigits;
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
ndigits = NUMERIC_NDIGITS(num);
alloc_var(dest, ndigits);
dest->weight = NUMERIC_WEIGHT(num);
1999-05-25 18:15:34 +02:00
dest->sign = NUMERIC_SIGN(num);
dest->dscale = NUMERIC_DSCALE(num);
memcpy(dest->digits, NUMERIC_DIGITS(num), ndigits * sizeof(NumericDigit));
}
/*
* init_var_from_num() -
*
* Initialize a variable from packed db format. The digits array is not
* copied, which saves some cycles when the resulting var is not modified.
* Also, there's no need to call free_var(), as long as you don't assign any
* other value to it (with set_var_* functions, or by using the var as the
* destination of a function like add_var())
*
* CAUTION: Do not modify the digits buffer of a var initialized with this
* function, e.g by calling round_var() or trunc_var(), as the changes will
* propagate to the original Numeric! It's OK to use it as the destination
* argument of one of the calculational functions, though.
*/
static void
init_var_from_num(Numeric num, NumericVar *dest)
{
dest->ndigits = NUMERIC_NDIGITS(num);
dest->weight = NUMERIC_WEIGHT(num);
dest->sign = NUMERIC_SIGN(num);
dest->dscale = NUMERIC_DSCALE(num);
dest->digits = NUMERIC_DIGITS(num);
dest->buf = NULL; /* digits array is not palloc'd */
}
/*
* set_var_from_var() -
*
* Copy one variable into another
*/
static void
set_var_from_var(const NumericVar *value, NumericVar *dest)
{
NumericDigit *newbuf;
newbuf = digitbuf_alloc(value->ndigits + 1);
newbuf[0] = 0; /* spare digit for rounding */
if (value->ndigits > 0) /* else value->digits might be null */
memcpy(newbuf + 1, value->digits,
value->ndigits * sizeof(NumericDigit));
digitbuf_free(dest->buf);
memmove(dest, value, sizeof(NumericVar));
1999-05-25 18:15:34 +02:00
dest->buf = newbuf;
dest->digits = newbuf + 1;
}
/*
* get_str_from_var() -
*
* Convert a var to text representation (guts of numeric_out).
* The var is displayed to the number of digits indicated by its dscale.
* Returns a palloc'd string.
*/
static char *
get_str_from_var(const NumericVar *var)
{
int dscale;
char *str;
char *cp;
char *endcp;
int i;
int d;
2003-08-04 02:43:34 +02:00
NumericDigit dig;
#if DEC_DIGITS > 1
2003-08-04 02:43:34 +02:00
NumericDigit d1;
#endif
dscale = var->dscale;
/*
* Allocate space for the result.
*
* i is set to the # of decimal digits before decimal point. dscale is the
* # of decimal digits we will print after decimal point. We may generate
* as many as DEC_DIGITS-1 excess digits at the end, and in addition we
* need room for sign, decimal point, null terminator.
*/
i = (var->weight + 1) * DEC_DIGITS;
if (i <= 0)
i = 1;
str = palloc(i + dscale + DEC_DIGITS + 2);
cp = str;
/*
* Output a dash for negative values
*/
if (var->sign == NUMERIC_NEG)
*cp++ = '-';
/*
* Output all digits before the decimal point
*/
if (var->weight < 0)
{
d = var->weight + 1;
*cp++ = '0';
}
else
{
for (d = 0; d <= var->weight; d++)
{
dig = (d < var->ndigits) ? var->digits[d] : 0;
/* In the first digit, suppress extra leading decimal zeroes */
#if DEC_DIGITS == 4
{
2003-08-04 02:43:34 +02:00
bool putit = (d > 0);
d1 = dig / 1000;
dig -= d1 * 1000;
putit |= (d1 > 0);
if (putit)
*cp++ = d1 + '0';
d1 = dig / 100;
dig -= d1 * 100;
putit |= (d1 > 0);
if (putit)
*cp++ = d1 + '0';
d1 = dig / 10;
dig -= d1 * 10;
putit |= (d1 > 0);
if (putit)
*cp++ = d1 + '0';
*cp++ = dig + '0';
}
#elif DEC_DIGITS == 2
d1 = dig / 10;
dig -= d1 * 10;
if (d1 > 0 || d > 0)
*cp++ = d1 + '0';
*cp++ = dig + '0';
#elif DEC_DIGITS == 1
*cp++ = dig + '0';
#else
#error unsupported NBASE
#endif
}
}
/*
2005-10-15 04:49:52 +02:00
* If requested, output a decimal point and all the digits that follow it.
* We initially put out a multiple of DEC_DIGITS digits, then truncate if
* needed.
*/
if (dscale > 0)
{
*cp++ = '.';
endcp = cp + dscale;
for (i = 0; i < dscale; d++, i += DEC_DIGITS)
{
dig = (d >= 0 && d < var->ndigits) ? var->digits[d] : 0;
#if DEC_DIGITS == 4
d1 = dig / 1000;
dig -= d1 * 1000;
*cp++ = d1 + '0';
d1 = dig / 100;
dig -= d1 * 100;
*cp++ = d1 + '0';
d1 = dig / 10;
dig -= d1 * 10;
*cp++ = d1 + '0';
*cp++ = dig + '0';
#elif DEC_DIGITS == 2
d1 = dig / 10;
dig -= d1 * 10;
*cp++ = d1 + '0';
*cp++ = dig + '0';
#elif DEC_DIGITS == 1
*cp++ = dig + '0';
#else
#error unsupported NBASE
#endif
}
cp = endcp;
}
/*
* terminate the string and return it
*/
*cp = '\0';
return str;
}
/*
* get_str_from_var_sci() -
*
* Convert a var to a normalised scientific notation text representation.
* This function does the heavy lifting for numeric_out_sci().
*
* This notation has the general form a * 10^b, where a is known as the
* "significand" and b is known as the "exponent".
*
* Because we can't do superscript in ASCII (and because we want to copy
* printf's behaviour) we display the exponent using E notation, with a
* minimum of two exponent digits.
*
* For example, the value 1234 could be output as 1.2e+03.
*
* We assume that the exponent can fit into an int32.
*
* rscale is the number of decimal digits desired after the decimal point in
* the output, negative values will be treated as meaning zero.
*
* Returns a palloc'd string.
*/
static char *
get_str_from_var_sci(const NumericVar *var, int rscale)
{
int32 exponent;
2010-02-26 03:01:40 +01:00
NumericVar denominator;
NumericVar significand;
int denom_scale;
size_t len;
char *str;
char *sig_out;
if (rscale < 0)
rscale = 0;
/*
* Determine the exponent of this number in normalised form.
*
* This is the exponent required to represent the number with only one
* significant digit before the decimal place.
*/
if (var->ndigits > 0)
{
exponent = (var->weight + 1) * DEC_DIGITS;
/*
* Compensate for leading decimal zeroes in the first numeric digit by
* decrementing the exponent.
*/
exponent -= DEC_DIGITS - (int) log10(var->digits[0]);
}
else
{
/*
* If var has no digits, then it must be zero.
*
* Zero doesn't technically have a meaningful exponent in normalised
* notation, but we just display the exponent as zero for consistency
* of output.
*/
exponent = 0;
}
/*
* 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.
*/
if (exponent < 0)
denom_scale = -exponent;
else
denom_scale = 0;
init_var(&denominator);
init_var(&significand);
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);
/*
* Allocate space for the result.
*
2010-02-26 03:01:40 +01:00
* In addition to the significand, we need room for the exponent
* decoration ("e"), the sign of the exponent, up to 10 digits for the
* exponent itself, and of course the null terminator.
*/
len = strlen(sig_out) + 13;
str = palloc(len);
snprintf(str, len, "%se%+03d", sig_out, exponent);
pfree(sig_out);
return str;
}
/*
* make_result_opt_error() -
*
* Create the packed db numeric format in palloc()'d memory from
* a variable. If "*have_error" flag is provided, on error it's set to
* true, NULL returned. This is helpful when caller need to handle errors
* by itself.
*/
static Numeric
make_result_opt_error(const NumericVar *var, bool *have_error)
{
1999-05-25 18:15:34 +02:00
Numeric result;
NumericDigit *digits = var->digits;
1999-05-25 18:15:34 +02:00
int weight = var->weight;
int sign = var->sign;
int n;
Size len;
if (have_error)
*have_error = false;
if (sign == NUMERIC_NAN)
{
result = (Numeric) palloc(NUMERIC_HDRSZ_SHORT);
SET_VARSIZE(result, NUMERIC_HDRSZ_SHORT);
result->choice.n_header = NUMERIC_NAN;
/* the header word is all we need */
dump_numeric("make_result()", result);
return result;
}
n = var->ndigits;
/* truncate leading zeroes */
while (n > 0 && *digits == 0)
{
digits++;
weight--;
n--;
}
/* truncate trailing zeroes */
while (n > 0 && digits[n - 1] == 0)
n--;
/* If zero result, force to weight=0 and positive sign */
if (n == 0)
{
weight = 0;
1999-05-25 18:15:34 +02:00
sign = NUMERIC_POS;
}
/* Build the result */
if (NUMERIC_CAN_BE_SHORT(var->dscale, weight))
{
len = NUMERIC_HDRSZ_SHORT + n * sizeof(NumericDigit);
result = (Numeric) palloc(len);
SET_VARSIZE(result, len);
result->choice.n_short.n_header =
(sign == NUMERIC_NEG ? (NUMERIC_SHORT | NUMERIC_SHORT_SIGN_MASK)
2011-04-10 17:42:00 +02:00
: NUMERIC_SHORT)
| (var->dscale << NUMERIC_SHORT_DSCALE_SHIFT)
| (weight < 0 ? NUMERIC_SHORT_WEIGHT_SIGN_MASK : 0)
| (weight & NUMERIC_SHORT_WEIGHT_MASK);
}
else
{
len = NUMERIC_HDRSZ + n * sizeof(NumericDigit);
result = (Numeric) palloc(len);
SET_VARSIZE(result, len);
result->choice.n_long.n_sign_dscale =
sign | (var->dscale & NUMERIC_DSCALE_MASK);
result->choice.n_long.n_weight = weight;
}
Assert(NUMERIC_NDIGITS(result) == n);
if (n > 0)
memcpy(NUMERIC_DIGITS(result), digits, n * sizeof(NumericDigit));
/* Check for overflow of int16 fields */
if (NUMERIC_WEIGHT(result) != weight ||
NUMERIC_DSCALE(result) != var->dscale)
{
if (have_error)
{
*have_error = true;
return NULL;
}
else
{
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value overflows numeric format")));
}
}
dump_numeric("make_result()", result);
return result;
}
/*
* make_result() -
*
* An interface to make_result_opt_error() without "have_error" argument.
*/
static Numeric
make_result(const NumericVar *var)
{
return make_result_opt_error(var, NULL);
}
/*
* apply_typmod() -
*
* Do bounds checking and rounding according to the attributes
* typmod field.
*/
static void
1999-05-26 00:43:53 +02:00
apply_typmod(NumericVar *var, int32 typmod)
{
int precision;
int scale;
int maxdigits;
int ddigits;
int i;
/* Do nothing if we have a default typmod (-1) */
1999-05-25 18:15:34 +02:00
if (typmod < (int32) (VARHDRSZ))
return;
typmod -= VARHDRSZ;
precision = (typmod >> 16) & 0xffff;
1999-05-25 18:15:34 +02:00
scale = typmod & 0xffff;
maxdigits = precision - scale;
/* Round to target scale (and set var->dscale) */
round_var(var, scale);
/*
* Check for overflow - note we can't do this before rounding, because
2005-10-15 04:49:52 +02:00
* rounding could raise the weight. Also note that the var's weight could
* be inflated by leading zeroes, which will be stripped before storage
* but perhaps might not have been yet. In any case, we must recognize a
* true zero, whose weight doesn't mean anything.
*/
ddigits = (var->weight + 1) * DEC_DIGITS;
if (ddigits > maxdigits)
{
/* Determine true weight; and check for all-zero result */
for (i = 0; i < var->ndigits; i++)
{
NumericDigit dig = var->digits[i];
if (dig)
{
/* Adjust for any high-order decimal zero digits */
#if DEC_DIGITS == 4
if (dig < 10)
ddigits -= 3;
else if (dig < 100)
ddigits -= 2;
else if (dig < 1000)
ddigits -= 1;
#elif DEC_DIGITS == 2
if (dig < 10)
ddigits -= 1;
#elif DEC_DIGITS == 1
/* no adjustment */
#else
#error unsupported NBASE
#endif
if (ddigits > maxdigits)
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("numeric field overflow"),
errdetail("A field with precision %d, scale %d must round to an absolute value less than %s%d.",
precision, scale,
2006-10-04 02:30:14 +02:00
/* Display 10^0 as 1 */
maxdigits ? "10^" : "",
maxdigits ? maxdigits : 1
)));
break;
}
ddigits -= DEC_DIGITS;
}
}
}
/*
* Convert numeric to int8, rounding if needed.
*
* If overflow, return false (no error is raised). Return true if okay.
*/
static bool
numericvar_to_int64(const NumericVar *var, int64 *result)
{
NumericDigit *digits;
int ndigits;
int weight;
int i;
int64 val;
bool neg;
NumericVar rounded;
/* Round to nearest integer */
init_var(&rounded);
set_var_from_var(var, &rounded);
round_var(&rounded, 0);
/* Check for zero input */
strip_var(&rounded);
ndigits = rounded.ndigits;
if (ndigits == 0)
{
*result = 0;
free_var(&rounded);
return true;
}
/*
2005-10-15 04:49:52 +02:00
* For input like 10000000000, we must treat stripped digits as real. So
* the loop assumes there are weight+1 digits before the decimal point.
*/
weight = rounded.weight;
2003-08-04 02:43:34 +02:00
Assert(weight >= 0 && ndigits <= weight + 1);
/*
* Construct the result. To avoid issues with converting a value
* corresponding to INT64_MIN (which can't be represented as a positive 64
* bit two's complement integer), accumulate value as a negative number.
*/
digits = rounded.digits;
neg = (rounded.sign == NUMERIC_NEG);
val = -digits[0];
for (i = 1; i <= weight; i++)
{
if (unlikely(pg_mul_s64_overflow(val, NBASE, &val)))
{
free_var(&rounded);
return false;
}
2003-08-04 02:43:34 +02:00
if (i < ndigits)
{
if (unlikely(pg_sub_s64_overflow(val, digits[i], &val)))
{
free_var(&rounded);
return false;
}
}
}
free_var(&rounded);
if (!neg)
{
if (unlikely(val == PG_INT64_MIN))
return false;
val = -val;
}
*result = val;
return true;
}
/*
* Convert int8 value to numeric.
*/
static void
int64_to_numericvar(int64 val, NumericVar *var)
{
uint64 uval,
newuval;
NumericDigit *ptr;
int ndigits;
/* int64 can require at most 19 decimal digits; add one for safety */
2003-08-04 02:43:34 +02:00
alloc_var(var, 20 / DEC_DIGITS);
if (val < 0)
{
var->sign = NUMERIC_NEG;
uval = -val;
}
else
{
var->sign = NUMERIC_POS;
uval = val;
}
var->dscale = 0;
if (val == 0)
{
var->ndigits = 0;
var->weight = 0;
return;
}
ptr = var->digits + var->ndigits;
ndigits = 0;
2003-08-04 02:43:34 +02:00
do
{
ptr--;
ndigits++;
newuval = uval / NBASE;
*ptr = uval - newuval * NBASE;
uval = newuval;
} while (uval);
var->digits = ptr;
var->ndigits = ndigits;
var->weight = ndigits - 1;
}
#ifdef HAVE_INT128
/*
* Convert numeric to int128, rounding if needed.
*
* If overflow, return false (no error is raised). Return true if okay.
*/
static bool
numericvar_to_int128(const NumericVar *var, int128 *result)
{
NumericDigit *digits;
int ndigits;
int weight;
int i;
int128 val,
oldval;
bool neg;
NumericVar rounded;
/* Round to nearest integer */
init_var(&rounded);
set_var_from_var(var, &rounded);
round_var(&rounded, 0);
/* Check for zero input */
strip_var(&rounded);
ndigits = rounded.ndigits;
if (ndigits == 0)
{
*result = 0;
free_var(&rounded);
return true;
}
/*
* For input like 10000000000, we must treat stripped digits as real. So
* the loop assumes there are weight+1 digits before the decimal point.
*/
weight = rounded.weight;
Assert(weight >= 0 && ndigits <= weight + 1);
/* Construct the result */
digits = rounded.digits;
neg = (rounded.sign == NUMERIC_NEG);
val = digits[0];
for (i = 1; i <= weight; i++)
{
oldval = val;
val *= NBASE;
if (i < ndigits)
val += digits[i];
/*
* The overflow check is a bit tricky because we want to accept
* INT128_MIN, which will overflow the positive accumulator. We can
* detect this case easily though because INT128_MIN is the only
* nonzero value for which -val == val (on a two's complement machine,
* anyway).
*/
if ((val / NBASE) != oldval) /* possible overflow? */
{
if (!neg || (-val) != val || val == 0 || oldval < 0)
{
free_var(&rounded);
return false;
}
}
}
free_var(&rounded);
*result = neg ? -val : val;
return true;
}
/*
* Convert 128 bit integer to numeric.
*/
static void
int128_to_numericvar(int128 val, NumericVar *var)
{
2015-05-24 03:35:49 +02:00
uint128 uval,
newuval;
NumericDigit *ptr;
int ndigits;
/* int128 can require at most 39 decimal digits; add one for safety */
alloc_var(var, 40 / DEC_DIGITS);
if (val < 0)
{
var->sign = NUMERIC_NEG;
uval = -val;
}
else
{
var->sign = NUMERIC_POS;
uval = val;
}
var->dscale = 0;
if (val == 0)
{
var->ndigits = 0;
var->weight = 0;
return;
}
ptr = var->digits + var->ndigits;
ndigits = 0;
do
{
ptr--;
ndigits++;
newuval = uval / NBASE;
*ptr = uval - newuval * NBASE;
uval = newuval;
} while (uval);
var->digits = ptr;
var->ndigits = ndigits;
var->weight = ndigits - 1;
}
#endif
/*
* Convert numeric to float8; if out of range, return +/- HUGE_VAL
*/
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 ... */
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
"double precision", tmp)));
}
pfree(tmp);
return val;
}
/* As above, but work from a NumericVar */
static double
numericvar_to_double_no_overflow(const NumericVar *var)
{
char *tmp;
double val;
char *endptr;
tmp = get_str_from_var(var);
/* unlike float8in, we ignore ERANGE from strtod */
val = strtod(tmp, &endptr);
if (*endptr != '\0')
{
/* shouldn't happen ... */
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("invalid input syntax for type %s: \"%s\"",
"double precision", tmp)));
}
pfree(tmp);
return val;
}
/*
* cmp_var() -
*
* Compare two values on variable level. We assume zeroes have been
* truncated to no digits.
*/
static int
cmp_var(const NumericVar *var1, const NumericVar *var2)
{
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
return cmp_var_common(var1->digits, var1->ndigits,
var1->weight, var1->sign,
var2->digits, var2->ndigits,
var2->weight, var2->sign);
}
/*
* cmp_var_common() -
*
2006-10-04 02:30:14 +02:00
* Main routine of cmp_var(). This function can be used by both
* NumericVar and Numeric.
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
*/
static int
cmp_var_common(const NumericDigit *var1digits, int var1ndigits,
int var1weight, int var1sign,
const NumericDigit *var2digits, int var2ndigits,
int var2weight, int var2sign)
{
if (var1ndigits == 0)
{
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
if (var2ndigits == 0)
return 0;
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
if (var2sign == NUMERIC_NEG)
return 1;
return -1;
}
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
if (var2ndigits == 0)
{
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
if (var1sign == NUMERIC_POS)
return 1;
return -1;
}
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
if (var1sign == NUMERIC_POS)
{
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
if (var2sign == NUMERIC_NEG)
return 1;
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
return cmp_abs_common(var1digits, var1ndigits, var1weight,
var2digits, var2ndigits, var2weight);
}
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
if (var2sign == NUMERIC_POS)
return -1;
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
return cmp_abs_common(var2digits, var2ndigits, var2weight,
var1digits, var1ndigits, var1weight);
}
/*
* add_var() -
*
* Full version of add functionality on variable level (handling signs).
* result might point to one of the operands too without danger.
*/
static void
add_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
{
/*
* Decide on the signs of the two variables what to do
*/
if (var1->sign == NUMERIC_POS)
{
if (var2->sign == NUMERIC_POS)
{
/*
* Both are positive result = +(ABS(var1) + ABS(var2))
*/
add_abs(var1, var2, result);
result->sign = NUMERIC_POS;
}
else
{
/*
2005-10-15 04:49:52 +02:00
* var1 is positive, var2 is negative Must compare absolute values
*/
switch (cmp_abs(var1, var2))
{
case 0:
/* ----------
* ABS(var1) == ABS(var2)
* result = ZERO
* ----------
*/
zero_var(result);
result->dscale = Max(var1->dscale, var2->dscale);
1999-05-25 18:15:34 +02:00
break;
case 1:
/* ----------
* ABS(var1) > ABS(var2)
* result = +(ABS(var1) - ABS(var2))
* ----------
*/
1999-05-25 18:15:34 +02:00
sub_abs(var1, var2, result);
result->sign = NUMERIC_POS;
break;
case -1:
/* ----------
* ABS(var1) < ABS(var2)
* result = -(ABS(var2) - ABS(var1))
* ----------
*/
1999-05-25 18:15:34 +02:00
sub_abs(var2, var1, result);
result->sign = NUMERIC_NEG;
break;
}
}
}
else
{
if (var2->sign == NUMERIC_POS)
{
/* ----------
* var1 is negative, var2 is positive
* Must compare absolute values
* ----------
*/
switch (cmp_abs(var1, var2))
{
case 0:
/* ----------
* ABS(var1) == ABS(var2)
* result = ZERO
* ----------
*/
zero_var(result);
result->dscale = Max(var1->dscale, var2->dscale);
1999-05-25 18:15:34 +02:00
break;
case 1:
/* ----------
* ABS(var1) > ABS(var2)
* result = -(ABS(var1) - ABS(var2))
* ----------
*/
1999-05-25 18:15:34 +02:00
sub_abs(var1, var2, result);
result->sign = NUMERIC_NEG;
break;
case -1:
/* ----------
* ABS(var1) < ABS(var2)
* result = +(ABS(var2) - ABS(var1))
* ----------
*/
1999-05-25 18:15:34 +02:00
sub_abs(var2, var1, result);
result->sign = NUMERIC_POS;
break;
}
}
else
{
/* ----------
* Both are negative
* result = -(ABS(var1) + ABS(var2))
* ----------
*/
add_abs(var1, var2, result);
result->sign = NUMERIC_NEG;
}
}
}
/*
* sub_var() -
*
* Full version of sub functionality on variable level (handling signs).
* result might point to one of the operands too without danger.
*/
static void
sub_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
{
/*
* Decide on the signs of the two variables what to do
*/
if (var1->sign == NUMERIC_POS)
{
if (var2->sign == NUMERIC_NEG)
{
/* ----------
* var1 is positive, var2 is negative
* result = +(ABS(var1) + ABS(var2))
* ----------
*/
add_abs(var1, var2, result);
result->sign = NUMERIC_POS;
}
else
{
/* ----------
* Both are positive
* Must compare absolute values
* ----------
*/
switch (cmp_abs(var1, var2))
{
case 0:
/* ----------
* ABS(var1) == ABS(var2)
* result = ZERO
* ----------
*/
zero_var(result);
result->dscale = Max(var1->dscale, var2->dscale);
1999-05-25 18:15:34 +02:00
break;
case 1:
/* ----------
* ABS(var1) > ABS(var2)
* result = +(ABS(var1) - ABS(var2))
* ----------
*/
1999-05-25 18:15:34 +02:00
sub_abs(var1, var2, result);
result->sign = NUMERIC_POS;
break;
case -1:
/* ----------
* ABS(var1) < ABS(var2)
* result = -(ABS(var2) - ABS(var1))
* ----------
*/
1999-05-25 18:15:34 +02:00
sub_abs(var2, var1, result);
result->sign = NUMERIC_NEG;
break;
}
}
}
else
{
if (var2->sign == NUMERIC_NEG)
{
/* ----------
* Both are negative
* Must compare absolute values
* ----------
*/
switch (cmp_abs(var1, var2))
{
case 0:
/* ----------
* ABS(var1) == ABS(var2)
* result = ZERO
* ----------
*/
zero_var(result);
result->dscale = Max(var1->dscale, var2->dscale);
1999-05-25 18:15:34 +02:00
break;
case 1:
/* ----------
* ABS(var1) > ABS(var2)
* result = -(ABS(var1) - ABS(var2))
* ----------
*/
1999-05-25 18:15:34 +02:00
sub_abs(var1, var2, result);
result->sign = NUMERIC_NEG;
break;
case -1:
/* ----------
* ABS(var1) < ABS(var2)
* result = +(ABS(var2) - ABS(var1))
* ----------
*/
1999-05-25 18:15:34 +02:00
sub_abs(var2, var1, result);
result->sign = NUMERIC_POS;
break;
}
}
else
{
/* ----------
* var1 is negative, var2 is positive
* result = -(ABS(var1) + ABS(var2))
* ----------
*/
add_abs(var1, var2, result);
result->sign = NUMERIC_NEG;
}
}
}
/*
* mul_var() -
*
* Multiplication on variable level. Product of var1 * var2 is stored
* in result. Result is rounded to no more than rscale fractional digits.
*/
static void
mul_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result,
int rscale)
{
1999-05-25 18:15:34 +02:00
int res_ndigits;
int res_sign;
int res_weight;
int maxdigits;
int *dig;
int carry;
int maxdig;
int newdig;
2015-11-14 20:55:38 +01:00
int var1ndigits;
int var2ndigits;
NumericDigit *var1digits;
NumericDigit *var2digits;
NumericDigit *res_digits;
1999-05-25 18:15:34 +02:00
int i,
i1,
i2;
2003-08-04 02:43:34 +02:00
2015-11-14 20:55:38 +01:00
/*
* Arrange for var1 to be the shorter of the two numbers. This improves
* performance because the inner multiplication loop is much simpler than
* the outer loop, so it's better to have a smaller number of iterations
* of the outer loop. This also reduces the number of times that the
* accumulator array needs to be normalized.
*/
if (var1->ndigits > var2->ndigits)
{
const NumericVar *tmp = var1;
2015-11-14 20:55:38 +01:00
var1 = var2;
var2 = tmp;
}
/* copy these values into local vars for speed in inner loop */
2015-11-14 20:55:38 +01:00
var1ndigits = var1->ndigits;
var2ndigits = var2->ndigits;
var1digits = var1->digits;
var2digits = var2->digits;
1999-05-25 18:15:34 +02:00
if (var1ndigits == 0 || var2ndigits == 0)
{
/* one or both inputs is zero; so is result */
zero_var(result);
result->dscale = rscale;
return;
}
/* Determine result sign and (maximum possible) weight */
if (var1->sign == var2->sign)
res_sign = NUMERIC_POS;
else
res_sign = NUMERIC_NEG;
res_weight = var1->weight + var2->weight + 2;
/*
2015-11-14 20:55:38 +01:00
* Determine the number of result digits to compute. If the exact result
2005-10-15 04:49:52 +02:00
* would have more than rscale fractional digits, truncate the computation
2015-11-14 20:55:38 +01:00
* with MUL_GUARD_DIGITS guard digits, i.e., ignore input digits that
* would only contribute to the right of that. (This will give the exact
* rounded-to-rscale answer unless carries out of the ignored positions
* would have propagated through more than MUL_GUARD_DIGITS digits.)
*
* Note: an exact computation could not produce more than var1ndigits +
* var2ndigits digits, but we allocate one extra output digit in case
* rscale-driven rounding produces a carry out of the highest exact digit.
*/
res_ndigits = var1ndigits + var2ndigits + 1;
2015-11-14 20:55:38 +01:00
maxdigits = res_weight + 1 + (rscale + DEC_DIGITS - 1) / DEC_DIGITS +
MUL_GUARD_DIGITS;
res_ndigits = Min(res_ndigits, maxdigits);
if (res_ndigits < 3)
{
2015-11-14 20:55:38 +01:00
/* All input digits will be ignored; so result is zero */
zero_var(result);
result->dscale = rscale;
return;
}
/*
* We do the arithmetic in an array "dig[]" of signed int's. Since
2005-10-15 04:49:52 +02:00
* INT_MAX is noticeably larger than NBASE*NBASE, this gives us headroom
* to avoid normalizing carries immediately.
*
2003-08-04 02:43:34 +02:00
* maxdig tracks the maximum possible value of any dig[] entry; when this
* threatens to exceed INT_MAX, we take the time to propagate carries.
* Furthermore, we need to ensure that overflow doesn't occur during the
* carry propagation passes either. The carry values could be as much as
* INT_MAX/NBASE, so really we must normalize when digits threaten to
* exceed INT_MAX - INT_MAX/NBASE.
*
* To avoid overflow in maxdig itself, it actually represents the max
* possible value divided by NBASE-1, ie, at the top of the loop it is
* known that no dig[] entry exceeds maxdig * (NBASE-1).
*/
dig = (int *) palloc0(res_ndigits * sizeof(int));
maxdig = 0;
2015-11-14 20:55:38 +01:00
/*
* The least significant digits of var1 should be ignored if they don't
* contribute directly to the first res_ndigits digits of the result that
* we are computing.
*
* Digit i1 of var1 and digit i2 of var2 are multiplied and added to digit
* i1+i2+2 of the accumulator array, so we need only consider digits of
* var1 for which i1 <= res_ndigits - 3.
*/
for (i1 = Min(var1ndigits - 1, res_ndigits - 3); i1 >= 0; i1--)
{
2003-08-04 02:43:34 +02:00
int var1digit = var1digits[i1];
if (var1digit == 0)
continue;
/* Time to normalize? */
maxdig += var1digit;
if (maxdig > (INT_MAX - INT_MAX / NBASE) / (NBASE - 1))
{
/* Yes, do it */
carry = 0;
2003-08-04 02:43:34 +02:00
for (i = res_ndigits - 1; i >= 0; i--)
{
newdig = dig[i] + carry;
if (newdig >= NBASE)
{
2003-08-04 02:43:34 +02:00
carry = newdig / NBASE;
newdig -= carry * NBASE;
}
else
carry = 0;
dig[i] = newdig;
}
Assert(carry == 0);
/* Reset maxdig to indicate new worst-case */
maxdig = 1 + var1digit;
}
2015-11-14 20:55:38 +01:00
/*
* Add the appropriate multiple of var2 into the accumulator.
*
* As above, digits of var2 can be ignored if they don't contribute,
* so we only include digits for which i1+i2+2 <= res_ndigits - 1.
*/
for (i2 = Min(var2ndigits - 1, res_ndigits - i1 - 3), i = i1 + i2 + 2;
i2 >= 0; i2--)
dig[i--] += var1digit * var2digits[i2];
}
/*
2005-10-15 04:49:52 +02:00
* Now we do a final carry propagation pass to normalize the result, which
* we combine with storing the result digits into the output. Note that
* this is still done at full precision w/guard digits.
*/
alloc_var(result, res_ndigits);
res_digits = result->digits;
carry = 0;
2003-08-04 02:43:34 +02:00
for (i = res_ndigits - 1; i >= 0; i--)
{
newdig = dig[i] + carry;
if (newdig >= NBASE)
{
2003-08-04 02:43:34 +02:00
carry = newdig / NBASE;
newdig -= carry * NBASE;
}
else
carry = 0;
res_digits[i] = newdig;
}
Assert(carry == 0);
pfree(dig);
/*
* Finally, round the result to the requested precision.
*/
1999-05-25 18:15:34 +02:00
result->weight = res_weight;
result->sign = res_sign;
/* Round to target rscale (and set result->dscale) */
round_var(result, rscale);
/* Strip leading and trailing zeroes */
strip_var(result);
}
/*
* div_var() -
*
* Division on variable level. Quotient of var1 / var2 is stored in result.
* The quotient is figured to exactly rscale fractional digits.
* If round is true, it is rounded at the rscale'th digit; if false, it
* is truncated (towards zero) at that digit.
*/
static void
div_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result,
int rscale, bool round)
{
int div_ndigits;
int res_ndigits;
int res_sign;
int res_weight;
int carry;
int borrow;
int divisor1;
int divisor2;
NumericDigit *dividend;
NumericDigit *divisor;
NumericDigit *res_digits;
int i;
int j;
/* copy these values into local vars for speed in inner loop */
int var1ndigits = var1->ndigits;
int var2ndigits = var2->ndigits;
/*
* First of all division by zero check; we must not be handed an
* unnormalized divisor.
*/
if (var2ndigits == 0 || var2->digits[0] == 0)
ereport(ERROR,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
/*
* Now result zero check
*/
if (var1ndigits == 0)
{
zero_var(result);
result->dscale = rscale;
return;
}
/*
* Determine the result sign, weight and number of digits to calculate.
* The weight figured here is correct if the emitted quotient has no
* leading zero digits; otherwise strip_var() will fix things up.
*/
if (var1->sign == var2->sign)
res_sign = NUMERIC_POS;
else
res_sign = NUMERIC_NEG;
res_weight = var1->weight - var2->weight;
/* The number of accurate result digits we need to produce: */
res_ndigits = res_weight + 1 + (rscale + DEC_DIGITS - 1) / DEC_DIGITS;
/* ... but always at least 1 */
res_ndigits = Max(res_ndigits, 1);
/* If rounding needed, figure one more digit to ensure correct result */
if (round)
res_ndigits++;
/*
* The working dividend normally requires res_ndigits + var2ndigits
* digits, but make it at least var1ndigits so we can load all of var1
* into it. (There will be an additional digit dividend[0] in the
* dividend space, but for consistency with Knuth's notation we don't
* count that in div_ndigits.)
*/
div_ndigits = res_ndigits + var2ndigits;
div_ndigits = Max(div_ndigits, var1ndigits);
/*
* We need a workspace with room for the working dividend (div_ndigits+1
* digits) plus room for the possibly-normalized divisor (var2ndigits
* digits). It is convenient also to have a zero at divisor[0] with the
* actual divisor data in divisor[1 .. var2ndigits]. Transferring the
* digits into the workspace also allows us to realloc the result (which
* might be the same as either input var) before we begin the main loop.
* Note that we use palloc0 to ensure that divisor[0], dividend[0], and
* any additional dividend positions beyond var1ndigits, start out 0.
*/
dividend = (NumericDigit *)
palloc0((div_ndigits + var2ndigits + 2) * sizeof(NumericDigit));
divisor = dividend + (div_ndigits + 1);
memcpy(dividend + 1, var1->digits, var1ndigits * sizeof(NumericDigit));
memcpy(divisor + 1, var2->digits, var2ndigits * sizeof(NumericDigit));
/*
* Now we can realloc the result to hold the generated quotient digits.
*/
alloc_var(result, res_ndigits);
res_digits = result->digits;
if (var2ndigits == 1)
{
/*
* If there's only a single divisor digit, we can use a fast path (cf.
* Knuth section 4.3.1 exercise 16).
*/
divisor1 = divisor[1];
carry = 0;
for (i = 0; i < res_ndigits; i++)
{
carry = carry * NBASE + dividend[i + 1];
res_digits[i] = carry / divisor1;
carry = carry % divisor1;
}
}
else
{
/*
* The full multiple-place algorithm is taken from Knuth volume 2,
* Algorithm 4.3.1D.
*
* We need the first divisor digit to be >= NBASE/2. If it isn't,
* make it so by scaling up both the divisor and dividend by the
* factor "d". (The reason for allocating dividend[0] above is to
* leave room for possible carry here.)
*/
if (divisor[1] < HALF_NBASE)
{
int d = NBASE / (divisor[1] + 1);
carry = 0;
for (i = var2ndigits; i > 0; i--)
{
carry += divisor[i] * d;
divisor[i] = carry % NBASE;
carry = carry / NBASE;
}
Assert(carry == 0);
carry = 0;
/* at this point only var1ndigits of dividend can be nonzero */
for (i = var1ndigits; i >= 0; i--)
{
carry += dividend[i] * d;
dividend[i] = carry % NBASE;
carry = carry / NBASE;
}
Assert(carry == 0);
Assert(divisor[1] >= HALF_NBASE);
}
/* First 2 divisor digits are used repeatedly in main loop */
divisor1 = divisor[1];
divisor2 = divisor[2];
/*
* Begin the main loop. Each iteration of this loop produces the j'th
* quotient digit by dividing dividend[j .. j + var2ndigits] by the
* divisor; this is essentially the same as the common manual
* procedure for long division.
*/
for (j = 0; j < res_ndigits; j++)
{
/* Estimate quotient digit from the first two dividend digits */
int next2digits = dividend[j] * NBASE + dividend[j + 1];
int qhat;
/*
* If next2digits are 0, then quotient digit must be 0 and there's
* no need to adjust the working dividend. It's worth testing
* here to fall out ASAP when processing trailing zeroes in a
* dividend.
*/
if (next2digits == 0)
{
res_digits[j] = 0;
continue;
}
if (dividend[j] == divisor1)
qhat = NBASE - 1;
else
qhat = next2digits / divisor1;
/*
* Adjust quotient digit if it's too large. Knuth proves that
* after this step, the quotient digit will be either correct or
* just one too large. (Note: it's OK to use dividend[j+2] here
* because we know the divisor length is at least 2.)
*/
while (divisor2 * qhat >
(next2digits - qhat * divisor1) * NBASE + dividend[j + 2])
qhat--;
/* As above, need do nothing more when quotient digit is 0 */
if (qhat > 0)
{
/*
* Multiply the divisor by qhat, and subtract that from the
* working dividend. "carry" tracks the multiplication,
* "borrow" the subtraction (could we fold these together?)
*/
carry = 0;
borrow = 0;
for (i = var2ndigits; i >= 0; i--)
{
carry += divisor[i] * qhat;
borrow -= carry % NBASE;
carry = carry / NBASE;
borrow += dividend[j + i];
if (borrow < 0)
{
dividend[j + i] = borrow + NBASE;
borrow = -1;
}
else
{
dividend[j + i] = borrow;
borrow = 0;
}
}
Assert(carry == 0);
/*
* If we got a borrow out of the top dividend digit, then
* indeed qhat was one too large. Fix it, and add back the
* divisor to correct the working dividend. (Knuth proves
* that this will occur only about 3/NBASE of the time; hence,
* it's a good idea to test this code with small NBASE to be
* sure this section gets exercised.)
*/
if (borrow)
{
qhat--;
carry = 0;
for (i = var2ndigits; i >= 0; i--)
{
carry += dividend[j + i] + divisor[i];
if (carry >= NBASE)
{
dividend[j + i] = carry - NBASE;
carry = 1;
}
else
{
dividend[j + i] = carry;
carry = 0;
}
}
/* A carry should occur here to cancel the borrow above */
Assert(carry == 1);
}
}
/* And we're done with this quotient digit */
res_digits[j] = qhat;
}
}
pfree(dividend);
/*
* Finally, round or truncate the result to the requested precision.
*/
result->weight = res_weight;
result->sign = res_sign;
/* Round or truncate to target rscale (and set result->dscale) */
if (round)
round_var(result, rscale);
else
trunc_var(result, rscale);
/* Strip leading and trailing zeroes */
strip_var(result);
}
/*
* div_var_fast() -
*
* This has the same API as div_var, but is implemented using the division
* algorithm from the "FM" library, rather than Knuth's schoolbook-division
* approach. This is significantly faster but can produce inaccurate
* results, because it sometimes has to propagate rounding to the left,
* and so we can never be entirely sure that we know the requested digits
* exactly. We compute DIV_GUARD_DIGITS extra digits, but there is
* no certainty that that's enough. We use this only in the transcendental
* function calculation routines, where everything is approximate anyway.
*
* Although we provide a "round" argument for consistency with div_var,
* it is unwise to use this function with round=false. In truncation mode
* it is possible to get a result with no significant digits, for example
* with rscale=0 we might compute 0.99999... and truncate that to 0 when
* the correct answer is 1.
*/
static void
div_var_fast(const NumericVar *var1, const NumericVar *var2,
NumericVar *result, int rscale, bool round)
1999-05-25 18:15:34 +02:00
{
int div_ndigits;
int load_ndigits;
1999-05-25 18:15:34 +02:00
int res_sign;
int res_weight;
int *div;
int qdigit;
int carry;
int maxdiv;
int newdig;
NumericDigit *res_digits;
double fdividend,
fdivisor,
fdivisorinverse,
fquotient;
int qi;
1999-05-25 18:15:34 +02:00
int i;
2003-08-04 02:43:34 +02:00
/* copy these values into local vars for speed in inner loop */
int var1ndigits = var1->ndigits;
int var2ndigits = var2->ndigits;
NumericDigit *var1digits = var1->digits;
NumericDigit *var2digits = var2->digits;
/*
* First of all division by zero check; we must not be handed an
* unnormalized divisor.
*/
if (var2ndigits == 0 || var2digits[0] == 0)
ereport(ERROR,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
/*
* Now result zero check
*/
if (var1ndigits == 0)
{
zero_var(result);
result->dscale = rscale;
return;
}
/*
* Determine the result sign, weight and number of digits to calculate
*/
if (var1->sign == var2->sign)
res_sign = NUMERIC_POS;
else
res_sign = NUMERIC_NEG;
res_weight = var1->weight - var2->weight + 1;
/* The number of accurate result digits we need to produce: */
2003-08-04 02:43:34 +02:00
div_ndigits = res_weight + 1 + (rscale + DEC_DIGITS - 1) / DEC_DIGITS;
/* Add guard digits for roundoff error */
div_ndigits += DIV_GUARD_DIGITS;
if (div_ndigits < DIV_GUARD_DIGITS)
div_ndigits = DIV_GUARD_DIGITS;
/*
* We do the arithmetic in an array "div[]" of signed int's. Since
2005-10-15 04:49:52 +02:00
* INT_MAX is noticeably larger than NBASE*NBASE, this gives us headroom
* to avoid normalizing carries immediately.
*
* We start with div[] containing one zero digit followed by the
* dividend's digits (plus appended zeroes to reach the desired precision
* including guard digits). Each step of the main loop computes an
* (approximate) quotient digit and stores it into div[], removing one
* position of dividend space. A final pass of carry propagation takes
* care of any mistaken quotient digits.
*
* Note that div[] doesn't necessarily contain all of the digits from the
* dividend --- the desired precision plus guard digits might be less than
* the dividend's precision. This happens, for example, in the square
* root algorithm, where we typically divide a 2N-digit number by an
* N-digit number, and only require a result with N digits of precision.
*/
div = (int *) palloc0((div_ndigits + 1) * sizeof(int));
load_ndigits = Min(div_ndigits, var1ndigits);
for (i = 0; i < load_ndigits; i++)
2003-08-04 02:43:34 +02:00
div[i + 1] = var1digits[i];
/*
2005-10-15 04:49:52 +02:00
* We estimate each quotient digit using floating-point arithmetic, taking
* the first four digits of the (current) dividend and divisor. This must
* be float to avoid overflow. The quotient digits will generally be off
* by no more than one from the exact answer.
*/
fdivisor = (double) var2digits[0];
for (i = 1; i < 4; i++)
{
fdivisor *= NBASE;
if (i < var2ndigits)
fdivisor += (double) var2digits[i];
}
fdivisorinverse = 1.0 / fdivisor;
/*
2005-10-15 04:49:52 +02:00
* maxdiv tracks the maximum possible absolute value of any div[] entry;
* when this threatens to exceed INT_MAX, we take the time to propagate
* carries. Furthermore, we need to ensure that overflow doesn't occur
* during the carry propagation passes either. The carry values may have
* an absolute value as high as INT_MAX/NBASE + 1, so really we must
* normalize when digits threaten to exceed INT_MAX - INT_MAX/NBASE - 1.
*
* To avoid overflow in maxdiv itself, it represents the max absolute
* value divided by NBASE-1, ie, at the top of the loop it is known that
* no div[] entry has an absolute value exceeding maxdiv * (NBASE-1).
*
* Actually, though, that holds good only for div[] entries after div[qi];
* the adjustment done at the bottom of the loop may cause div[qi + 1] to
* exceed the maxdiv limit, so that div[qi] in the next iteration is
* beyond the limit. This does not cause problems, as explained below.
*/
maxdiv = 1;
1999-05-25 18:15:34 +02:00
/*
* Outer loop computes next quotient digit, which will go into div[qi]
*/
for (qi = 0; qi < div_ndigits; qi++)
{
/* Approximate the current dividend value */
fdividend = (double) div[qi];
for (i = 1; i < 4; i++)
{
fdividend *= NBASE;
2003-08-04 02:43:34 +02:00
if (qi + i <= div_ndigits)
fdividend += (double) div[qi + i];
}
/* Compute the (approximate) quotient digit */
fquotient = fdividend * fdivisorinverse;
qdigit = (fquotient >= 0.0) ? ((int) fquotient) :
2003-08-04 02:43:34 +02:00
(((int) fquotient) - 1); /* truncate towards -infinity */
if (qdigit != 0)
{
/* Do we need to normalize now? */
maxdiv += Abs(qdigit);
if (maxdiv > (INT_MAX - INT_MAX / NBASE - 1) / (NBASE - 1))
{
/*
* Yes, do it. Note that if var2ndigits is much smaller than
* div_ndigits, we can save a significant amount of effort
* here by noting that we only need to normalise those div[]
* entries touched where prior iterations subtracted multiples
* of the divisor.
*/
carry = 0;
for (i = Min(qi + var2ndigits - 2, div_ndigits); i > qi; i--)
{
newdig = div[i] + carry;
if (newdig < 0)
{
2003-08-04 02:43:34 +02:00
carry = -((-newdig - 1) / NBASE) - 1;
newdig -= carry * NBASE;
}
else if (newdig >= NBASE)
{
2003-08-04 02:43:34 +02:00
carry = newdig / NBASE;
newdig -= carry * NBASE;
}
else
carry = 0;
div[i] = newdig;
}
newdig = div[qi] + carry;
div[qi] = newdig;
2003-08-04 02:43:34 +02:00
/*
2005-10-15 04:49:52 +02:00
* All the div[] digits except possibly div[qi] are now in the
* range 0..NBASE-1. We do not need to consider div[qi] in
* the maxdiv value anymore, so we can reset maxdiv to 1.
*/
maxdiv = 1;
2003-08-04 02:43:34 +02:00
/*
* Recompute the quotient digit since new info may have
* propagated into the top four dividend digits
*/
fdividend = (double) div[qi];
for (i = 1; i < 4; i++)
{
fdividend *= NBASE;
2003-08-04 02:43:34 +02:00
if (qi + i <= div_ndigits)
fdividend += (double) div[qi + i];
}
/* Compute the (approximate) quotient digit */
fquotient = fdividend * fdivisorinverse;
qdigit = (fquotient >= 0.0) ? ((int) fquotient) :
2005-10-15 04:49:52 +02:00
(((int) fquotient) - 1); /* truncate towards -infinity */
maxdiv += Abs(qdigit);
}
/*
* Subtract off the appropriate multiple of the divisor.
*
* The digits beyond div[qi] cannot overflow, because we know they
* will fall within the maxdiv limit. As for div[qi] itself, note
* that qdigit is approximately trunc(div[qi] / vardigits[0]),
* which would make the new value simply div[qi] mod vardigits[0].
* The lower-order terms in qdigit can change this result by not
* more than about twice INT_MAX/NBASE, so overflow is impossible.
*/
if (qdigit != 0)
{
2003-08-04 02:43:34 +02:00
int istop = Min(var2ndigits, div_ndigits - qi + 1);
for (i = 0; i < istop; i++)
2003-08-04 02:43:34 +02:00
div[qi + i] -= qdigit * var2digits[i];
}
}
/*
2005-10-15 04:49:52 +02:00
* The dividend digit we are about to replace might still be nonzero.
* Fold it into the next digit position.
*
* There is no risk of overflow here, although proving that requires
* some care. Much as with the argument for div[qi] not overflowing,
* if we consider the first two terms in the numerator and denominator
* of qdigit, we can see that the final value of div[qi + 1] will be
* approximately a remainder mod (vardigits[0]*NBASE + vardigits[1]).
* Accounting for the lower-order terms is a bit complicated but ends
* up adding not much more than INT_MAX/NBASE to the possible range.
* Thus, div[qi + 1] cannot overflow here, and in its role as div[qi]
* in the next loop iteration, it can't be large enough to cause
* overflow in the carry propagation step (if any), either.
*
* But having said that: div[qi] can be more than INT_MAX/NBASE, as
* noted above, which means that the product div[qi] * NBASE *can*
* overflow. When that happens, adding it to div[qi + 1] will always
2016-07-15 04:48:26 +02:00
* cause a canceling overflow so that the end result is correct. We
* could avoid the intermediate overflow by doing the multiplication
* and addition in int64 arithmetic, but so far there appears no need.
*/
2003-08-04 02:43:34 +02:00
div[qi + 1] += div[qi] * NBASE;
div[qi] = qdigit;
}
/*
* Approximate and store the last quotient digit (div[div_ndigits])
*/
fdividend = (double) div[qi];
for (i = 1; i < 4; i++)
fdividend *= NBASE;
fquotient = fdividend * fdivisorinverse;
qdigit = (fquotient >= 0.0) ? ((int) fquotient) :
2003-08-04 02:43:34 +02:00
(((int) fquotient) - 1); /* truncate towards -infinity */
div[qi] = qdigit;
/*
* Because the quotient digits might be off by one, some of them might be
* -1 or NBASE at this point. The represented value is correct in a
* mathematical sense, but it doesn't look right. We do a final carry
* propagation pass to normalize the digits, which we combine with storing
* the result digits into the output. Note that this is still done at
* full precision w/guard digits.
*/
2003-08-04 02:43:34 +02:00
alloc_var(result, div_ndigits + 1);
res_digits = result->digits;
carry = 0;
for (i = div_ndigits; i >= 0; i--)
{
newdig = div[i] + carry;
if (newdig < 0)
{
2003-08-04 02:43:34 +02:00
carry = -((-newdig - 1) / NBASE) - 1;
newdig -= carry * NBASE;
}
else if (newdig >= NBASE)
{
2003-08-04 02:43:34 +02:00
carry = newdig / NBASE;
newdig -= carry * NBASE;
}
else
carry = 0;
res_digits[i] = newdig;
}
Assert(carry == 0);
pfree(div);
/*
* Finally, round the result to the requested precision.
*/
result->weight = res_weight;
result->sign = res_sign;
/* Round to target rscale (and set result->dscale) */
if (round)
round_var(result, rscale);
else
trunc_var(result, rscale);
2005-10-15 04:49:52 +02:00
/* Strip leading and trailing zeroes */
strip_var(result);
}
/*
* Default scale selection for division
*
* Returns the appropriate result scale for the division result.
*/
static int
select_div_scale(const NumericVar *var1, const NumericVar *var2)
{
int weight1,
weight2,
qweight,
i;
NumericDigit firstdigit1,
firstdigit2;
int rscale;
/*
2005-10-15 04:49:52 +02:00
* The result scale of a division isn't specified in any SQL standard. For
* PostgreSQL we select a result scale that will give at least
* NUMERIC_MIN_SIG_DIGITS significant digits, so that numeric gives a
* result no less accurate than float8; but use a scale not less than
* either input's display scale.
*/
/* 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;
}
}
/*
2003-08-04 02:43:34 +02:00
* 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 result scale */
rscale = NUMERIC_MIN_SIG_DIGITS - qweight * DEC_DIGITS;
rscale = Max(rscale, var1->dscale);
rscale = Max(rscale, var2->dscale);
rscale = Max(rscale, NUMERIC_MIN_DISPLAY_SCALE);
rscale = Min(rscale, NUMERIC_MAX_DISPLAY_SCALE);
return rscale;
}
/*
* mod_var() -
*
* Calculate the modulo of two numerics at variable level
*/
static void
mod_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
{
1999-05-25 18:15:34 +02:00
NumericVar tmp;
init_var(&tmp);
/* ---------
* We do this using the equation
* mod(x,y) = x - trunc(x/y)*y
* div_var can be persuaded to give us trunc(x/y) directly.
* ----------
*/
div_var(var1, var2, &tmp, 0, false);
mul_var(var2, &tmp, &tmp, var2->dscale);
sub_var(var1, &tmp, result);
free_var(&tmp);
}
/*
* div_mod_var() -
*
* Calculate the truncated integer quotient and numeric remainder of two
* numeric variables. The remainder is precise to var2's dscale.
*/
static void
div_mod_var(const NumericVar *var1, const NumericVar *var2,
NumericVar *quot, NumericVar *rem)
{
NumericVar q;
NumericVar r;
init_var(&q);
init_var(&r);
/*
* Use div_var_fast() to get an initial estimate for the integer quotient.
* This might be inaccurate (per the warning in div_var_fast's comments),
* but we can correct it below.
*/
div_var_fast(var1, var2, &q, 0, false);
/* Compute initial estimate of remainder using the quotient estimate. */
mul_var(var2, &q, &r, var2->dscale);
sub_var(var1, &r, &r);
/*
* Adjust the results if necessary --- the remainder should have the same
* sign as var1, and its absolute value should be less than the absolute
* value of var2.
*/
while (r.ndigits != 0 && r.sign != var1->sign)
{
/* The absolute value of the quotient is too large */
if (var1->sign == var2->sign)
{
sub_var(&q, &const_one, &q);
add_var(&r, var2, &r);
}
else
{
add_var(&q, &const_one, &q);
sub_var(&r, var2, &r);
}
}
while (cmp_abs(&r, var2) >= 0)
{
/* The absolute value of the quotient is too small */
if (var1->sign == var2->sign)
{
add_var(&q, &const_one, &q);
sub_var(&r, var2, &r);
}
else
{
sub_var(&q, &const_one, &q);
add_var(&r, var2, &r);
}
}
set_var_from_var(&q, quot);
set_var_from_var(&r, rem);
free_var(&q);
free_var(&r);
}
/*
* ceil_var() -
*
* Return the smallest integer greater than or equal to the argument
* on variable level
*/
static void
ceil_var(const NumericVar *var, NumericVar *result)
{
1999-05-25 18:15:34 +02:00
NumericVar tmp;
init_var(&tmp);
set_var_from_var(var, &tmp);
trunc_var(&tmp, 0);
if (var->sign == NUMERIC_POS && cmp_var(var, &tmp) != 0)
add_var(&tmp, &const_one, &tmp);
set_var_from_var(&tmp, result);
free_var(&tmp);
}
/*
* floor_var() -
*
* Return the largest integer equal to or less than the argument
* on variable level
*/
static void
floor_var(const NumericVar *var, NumericVar *result)
{
1999-05-25 18:15:34 +02:00
NumericVar tmp;
init_var(&tmp);
set_var_from_var(var, &tmp);
trunc_var(&tmp, 0);
if (var->sign == NUMERIC_NEG && cmp_var(var, &tmp) != 0)
sub_var(&tmp, &const_one, &tmp);
set_var_from_var(&tmp, result);
free_var(&tmp);
}
/*
* gcd_var() -
*
* Calculate the greatest common divisor of two numerics at variable level
*/
static void
gcd_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
{
int res_dscale;
int cmp;
NumericVar tmp_arg;
NumericVar mod;
res_dscale = Max(var1->dscale, var2->dscale);
/*
* Arrange for var1 to be the number with the greater absolute value.
*
* This would happen automatically in the loop below, but avoids an
* expensive modulo operation.
*/
cmp = cmp_abs(var1, var2);
if (cmp < 0)
{
const NumericVar *tmp = var1;
var1 = var2;
var2 = tmp;
}
/*
* Also avoid the taking the modulo if the inputs have the same absolute
* value, or if the smaller input is zero.
*/
if (cmp == 0 || var2->ndigits == 0)
{
set_var_from_var(var1, result);
result->sign = NUMERIC_POS;
result->dscale = res_dscale;
return;
}
init_var(&tmp_arg);
init_var(&mod);
/* Use the Euclidean algorithm to find the GCD */
set_var_from_var(var1, &tmp_arg);
set_var_from_var(var2, result);
for (;;)
{
/* this loop can take a while, so allow it to be interrupted */
CHECK_FOR_INTERRUPTS();
mod_var(&tmp_arg, result, &mod);
if (mod.ndigits == 0)
break;
set_var_from_var(result, &tmp_arg);
set_var_from_var(&mod, result);
}
result->sign = NUMERIC_POS;
result->dscale = res_dscale;
free_var(&tmp_arg);
free_var(&mod);
}
/*
* sqrt_var() -
*
* Compute the square root of x using the Karatsuba Square Root algorithm.
* NOTE: we allow rscale < 0 here, implying rounding before the decimal
* point.
*/
static void
sqrt_var(const NumericVar *arg, NumericVar *result, int rscale)
{
1999-05-25 18:15:34 +02:00
int stat;
int res_weight;
int res_ndigits;
int src_ndigits;
int step;
int ndigits[32];
int blen;
int64 arg_int64;
int src_idx;
int64 s_int64;
int64 r_int64;
NumericVar s_var;
NumericVar r_var;
NumericVar a0_var;
NumericVar a1_var;
NumericVar q_var;
NumericVar u_var;
stat = cmp_var(arg, &const_zero);
if (stat == 0)
{
zero_var(result);
result->dscale = rscale;
return;
}
/*
2005-10-15 04:49:52 +02:00
* SQL2003 defines sqrt() in terms of power, so we need to emit the right
* SQLSTATE error code if the operand is negative.
*/
if (stat < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
errmsg("cannot take square root of a negative number")));
init_var(&s_var);
init_var(&r_var);
init_var(&a0_var);
init_var(&a1_var);
init_var(&q_var);
init_var(&u_var);
/*
* The result weight is half the input weight, rounded towards minus
* infinity --- res_weight = floor(arg->weight / 2).
*/
if (arg->weight >= 0)
res_weight = arg->weight / 2;
else
res_weight = -((-arg->weight - 1) / 2 + 1);
/*
* Number of NBASE digits to compute. To ensure correct rounding, compute
* at least 1 extra decimal digit. We explicitly allow rscale to be
* negative here, but must always compute at least 1 NBASE digit. Thus
* res_ndigits = res_weight + 1 + ceil((rscale + 1) / DEC_DIGITS) or 1.
*/
if (rscale + 1 >= 0)
res_ndigits = res_weight + 1 + (rscale + DEC_DIGITS) / DEC_DIGITS;
else
res_ndigits = res_weight + 1 - (-rscale - 1) / DEC_DIGITS;
res_ndigits = Max(res_ndigits, 1);
/*
* Number of source NBASE digits logically required to produce a result
* with this precision --- every digit before the decimal point, plus 2
* for each result digit after the decimal point (or minus 2 for each
* result digit we round before the decimal point).
*/
src_ndigits = arg->weight + 1 + (res_ndigits - res_weight - 1) * 2;
src_ndigits = Max(src_ndigits, 1);
/* ----------
* From this point on, we treat the input and the result as integers and
* compute the integer square root and remainder using the Karatsuba
* Square Root algorithm, which may be written recursively as follows:
*
* SqrtRem(n = a3*b^3 + a2*b^2 + a1*b + a0):
* [ for some base b, and coefficients a0,a1,a2,a3 chosen so that
* 0 <= a0,a1,a2 < b and a3 >= b/4 ]
* Let (s,r) = SqrtRem(a3*b + a2)
* Let (q,u) = DivRem(r*b + a1, 2*s)
* Let s = s*b + q
* Let r = u*b + a0 - q^2
* If r < 0 Then
* Let r = r + s
* Let s = s - 1
* Let r = r + s
* Return (s,r)
*
* See "Karatsuba Square Root", Paul Zimmermann, INRIA Research Report
* RR-3805, November 1999. At the time of writing this was available
* on the net at <https://hal.inria.fr/inria-00072854>.
*
* The way to read the assumption "n = a3*b^3 + a2*b^2 + a1*b + a0" is
* "choose a base b such that n requires at least four base-b digits to
* express; then those digits are a3,a2,a1,a0, with a3 possibly larger
* than b". For optimal performance, b should have approximately a
* quarter the number of digits in the input, so that the outer square
* root computes roughly twice as many digits as the inner one. For
* simplicity, we choose b = NBASE^blen, an integer power of NBASE.
*
* We implement the algorithm iteratively rather than recursively, to
* allow the working variables to be reused. With this approach, each
* digit of the input is read precisely once --- src_idx tracks the number
* of input digits used so far.
*
* The array ndigits[] holds the number of NBASE digits of the input that
* will have been used at the end of each iteration, which roughly doubles
* each time. Note that the array elements are stored in reverse order,
* so if the final iteration requires src_ndigits = 37 input digits, the
* array will contain [37,19,11,7,5,3], and we would start by computing
* the square root of the 3 most significant NBASE digits.
*
* In each iteration, we choose blen to be the largest integer for which
* the input number has a3 >= b/4, when written in the form above. In
* general, this means blen = src_ndigits / 4 (truncated), but if
* src_ndigits is a multiple of 4, that might lead to the coefficient a3
* being less than b/4 (if the first input digit is less than NBASE/4), in
* which case we choose blen = src_ndigits / 4 - 1. The number of digits
* in the inner square root is then src_ndigits - 2*blen. So, for
* example, if we have src_ndigits = 26 initially, the array ndigits[]
* will be either [26,14,8,4] or [26,14,8,6,4], depending on the size of
* the first input digit.
*
* Additionally, we can put an upper bound on the number of steps required
* as follows --- suppose that the number of source digits is an n-bit
* number in the range [2^(n-1), 2^n-1], then blen will be in the range
* [2^(n-3)-1, 2^(n-2)-1] and the number of digits in the inner square
* root will be in the range [2^(n-2), 2^(n-1)+1]. In the next step, blen
* will be in the range [2^(n-4)-1, 2^(n-3)] and the number of digits in
* the next inner square root will be in the range [2^(n-3), 2^(n-2)+1].
* This pattern repeats, and in the worst case the array ndigits[] will
* contain [2^n-1, 2^(n-1)+1, 2^(n-2)+1, ... 9, 5, 3], and the computation
* will require n steps. Therefore, since all digit array sizes are
* signed 32-bit integers, the number of steps required is guaranteed to
* be less than 32.
* ----------
*/
step = 0;
while ((ndigits[step] = src_ndigits) > 4)
{
/* Choose b so that a3 >= b/4, as described above */
blen = src_ndigits / 4;
if (blen * 4 == src_ndigits && arg->digits[0] < NBASE / 4)
blen--;
/* Number of digits in the next step (inner square root) */
src_ndigits -= 2 * blen;
step++;
}
/*
* First iteration (innermost square root and remainder):
*
* Here src_ndigits <= 4, and the input fits in an int64. Its square root
* has at most 9 decimal digits, so estimate it using double precision
* arithmetic, which will in fact almost certainly return the correct
* result with no further correction required.
*/
arg_int64 = arg->digits[0];
for (src_idx = 1; src_idx < src_ndigits; src_idx++)
{
arg_int64 *= NBASE;
if (src_idx < arg->ndigits)
arg_int64 += arg->digits[src_idx];
}
s_int64 = (int64) sqrt((double) arg_int64);
r_int64 = arg_int64 - s_int64 * s_int64;
/*
* Use Newton's method to correct the result, if necessary.
*
* This uses integer division with truncation to compute the truncated
* integer square root by iterating using the formula x -> (x + n/x) / 2.
* This is known to converge to isqrt(n), unless n+1 is a perfect square.
* If n+1 is a perfect square, the sequence will oscillate between the two
* values isqrt(n) and isqrt(n)+1, so we can be assured of convergence by
* checking the remainder.
*/
while (r_int64 < 0 || r_int64 > 2 * s_int64)
{
s_int64 = (s_int64 + arg_int64 / s_int64) / 2;
r_int64 = arg_int64 - s_int64 * s_int64;
}
/*
* Iterations with src_ndigits <= 8:
*
* The next 1 or 2 iterations compute larger (outer) square roots with
* src_ndigits <= 8, so the result still fits in an int64 (even though the
* input no longer does) and we can continue to compute using int64
* variables to avoid more expensive numeric computations.
*
* It is fairly easy to see that there is no risk of the intermediate
* values below overflowing 64-bit integers. In the worst case, the
* previous iteration will have computed a 3-digit square root (of a
* 6-digit input less than NBASE^6 / 4), so at the start of this
* iteration, s will be less than NBASE^3 / 2 = 10^12 / 2, and r will be
* less than 10^12. In this case, blen will be 1, so numer will be less
* than 10^17, and denom will be less than 10^12 (and hence u will also be
* less than 10^12). Finally, since q^2 = u*b + a0 - r, we can also be
* sure that q^2 < 10^17. Therefore all these quantities fit comfortably
* in 64-bit integers.
*/
step--;
while (step >= 0 && (src_ndigits = ndigits[step]) <= 8)
{
int b;
int a0;
int a1;
int i;
int64 numer;
int64 denom;
int64 q;
int64 u;
blen = (src_ndigits - src_idx) / 2;
/* Extract a1 and a0, and compute b */
a0 = 0;
a1 = 0;
b = 1;
for (i = 0; i < blen; i++, src_idx++)
{
b *= NBASE;
a1 *= NBASE;
if (src_idx < arg->ndigits)
a1 += arg->digits[src_idx];
}
for (i = 0; i < blen; i++, src_idx++)
{
a0 *= NBASE;
if (src_idx < arg->ndigits)
a0 += arg->digits[src_idx];
}
/* Compute (q,u) = DivRem(r*b + a1, 2*s) */
numer = r_int64 * b + a1;
denom = 2 * s_int64;
q = numer / denom;
u = numer - q * denom;
/* Compute s = s*b + q and r = u*b + a0 - q^2 */
s_int64 = s_int64 * b + q;
r_int64 = u * b + a0 - q * q;
if (r_int64 < 0)
{
/* s is too large by 1; set r += s, s--, r += s */
r_int64 += s_int64;
s_int64--;
r_int64 += s_int64;
}
Assert(src_idx == src_ndigits); /* All input digits consumed */
step--;
}
/*
* On platforms with 128-bit integer support, we can further delay the
* need to use numeric variables.
*/
#ifdef HAVE_INT128
if (step >= 0)
{
int128 s_int128;
int128 r_int128;
s_int128 = s_int64;
r_int128 = r_int64;
/*
* Iterations with src_ndigits <= 16:
*
* The result fits in an int128 (even though the input doesn't) so we
* use int128 variables to avoid more expensive numeric computations.
*/
while (step >= 0 && (src_ndigits = ndigits[step]) <= 16)
{
int64 b;
int64 a0;
int64 a1;
int64 i;
int128 numer;
int128 denom;
int128 q;
int128 u;
blen = (src_ndigits - src_idx) / 2;
/* Extract a1 and a0, and compute b */
a0 = 0;
a1 = 0;
b = 1;
for (i = 0; i < blen; i++, src_idx++)
{
b *= NBASE;
a1 *= NBASE;
if (src_idx < arg->ndigits)
a1 += arg->digits[src_idx];
}
for (i = 0; i < blen; i++, src_idx++)
{
a0 *= NBASE;
if (src_idx < arg->ndigits)
a0 += arg->digits[src_idx];
}
/* Compute (q,u) = DivRem(r*b + a1, 2*s) */
numer = r_int128 * b + a1;
denom = 2 * s_int128;
q = numer / denom;
u = numer - q * denom;
/* Compute s = s*b + q and r = u*b + a0 - q^2 */
s_int128 = s_int128 * b + q;
r_int128 = u * b + a0 - q * q;
if (r_int128 < 0)
{
/* s is too large by 1; set r += s, s--, r += s */
r_int128 += s_int128;
s_int128--;
r_int128 += s_int128;
}
Assert(src_idx == src_ndigits); /* All input digits consumed */
step--;
}
/*
* All remaining iterations require numeric variables. Convert the
* integer values to NumericVar and continue. Note that in the final
* iteration we don't need the remainder, so we can save a few cycles
* there by not fully computing it.
*/
int128_to_numericvar(s_int128, &s_var);
if (step >= 0)
int128_to_numericvar(r_int128, &r_var);
}
else
{
int64_to_numericvar(s_int64, &s_var);
/* step < 0, so we certainly don't need r */
}
#else /* !HAVE_INT128 */
int64_to_numericvar(s_int64, &s_var);
if (step >= 0)
int64_to_numericvar(r_int64, &r_var);
#endif /* HAVE_INT128 */
/*
* The remaining iterations with src_ndigits > 8 (or 16, if have int128)
* use numeric variables.
*/
while (step >= 0)
{
int tmp_len;
src_ndigits = ndigits[step];
blen = (src_ndigits - src_idx) / 2;
/* Extract a1 and a0 */
if (src_idx < arg->ndigits)
{
tmp_len = Min(blen, arg->ndigits - src_idx);
alloc_var(&a1_var, tmp_len);
memcpy(a1_var.digits, arg->digits + src_idx,
tmp_len * sizeof(NumericDigit));
a1_var.weight = blen - 1;
a1_var.sign = NUMERIC_POS;
a1_var.dscale = 0;
strip_var(&a1_var);
}
else
{
zero_var(&a1_var);
a1_var.dscale = 0;
}
src_idx += blen;
if (src_idx < arg->ndigits)
{
tmp_len = Min(blen, arg->ndigits - src_idx);
alloc_var(&a0_var, tmp_len);
memcpy(a0_var.digits, arg->digits + src_idx,
tmp_len * sizeof(NumericDigit));
a0_var.weight = blen - 1;
a0_var.sign = NUMERIC_POS;
a0_var.dscale = 0;
strip_var(&a0_var);
}
else
{
zero_var(&a0_var);
a0_var.dscale = 0;
}
src_idx += blen;
/* Compute (q,u) = DivRem(r*b + a1, 2*s) */
set_var_from_var(&r_var, &q_var);
q_var.weight += blen;
add_var(&q_var, &a1_var, &q_var);
add_var(&s_var, &s_var, &u_var);
div_mod_var(&q_var, &u_var, &q_var, &u_var);
/* Compute s = s*b + q */
s_var.weight += blen;
add_var(&s_var, &q_var, &s_var);
/*
* Compute r = u*b + a0 - q^2.
*
* In the final iteration, we don't actually need r; we just need to
* know whether it is negative, so that we know whether to adjust s.
* So instead of the final subtraction we can just compare.
*/
u_var.weight += blen;
add_var(&u_var, &a0_var, &u_var);
mul_var(&q_var, &q_var, &q_var, 0);
if (step > 0)
{
/* Need r for later iterations */
sub_var(&u_var, &q_var, &r_var);
if (r_var.sign == NUMERIC_NEG)
{
/* s is too large by 1; set r += s, s--, r += s */
add_var(&r_var, &s_var, &r_var);
sub_var(&s_var, &const_one, &s_var);
add_var(&r_var, &s_var, &r_var);
}
}
else
{
/* Don't need r anymore, except to test if s is too large by 1 */
if (cmp_var(&u_var, &q_var) < 0)
sub_var(&s_var, &const_one, &s_var);
}
Assert(src_idx == src_ndigits); /* All input digits consumed */
step--;
}
/*
* Construct the final result, rounding it to the requested precision.
*/
set_var_from_var(&s_var, result);
result->weight = res_weight;
result->sign = NUMERIC_POS;
/* Round to target rscale (and set result->dscale) */
round_var(result, rscale);
/* Strip leading and trailing zeroes */
strip_var(result);
free_var(&s_var);
free_var(&r_var);
free_var(&a0_var);
free_var(&a1_var);
free_var(&q_var);
free_var(&u_var);
}
/*
* exp_var() -
*
2015-11-14 20:55:38 +01:00
* Raise e to the power of x, computed to rscale fractional digits
*/
static void
exp_var(const NumericVar *arg, NumericVar *result, int rscale)
1999-05-25 18:15:34 +02:00
{
NumericVar x;
2015-11-14 20:55:38 +01:00
NumericVar elem;
NumericVar ni;
double val;
int dweight;
int ndiv2;
int sig_digits;
int local_rscale;
init_var(&x);
2015-11-14 20:55:38 +01:00
init_var(&elem);
init_var(&ni);
set_var_from_var(arg, &x);
2015-11-14 20:55:38 +01:00
/*
* Estimate the dweight of the result using floating point arithmetic, so
* that we can choose an appropriate local rscale for the calculation.
*/
val = numericvar_to_double_no_overflow(&x);
2015-11-14 20:55:38 +01:00
/* Guard against overflow */
Fix corner-case loss of precision in numeric pow() calculation Commit 7d9a4737c268f61fb8800957631f12d3f13be218 greatly improved the accuracy of the numeric transcendental functions, however it failed to consider the case where the result from pow() is close to the overflow threshold, for example 0.12 ^ -2345.6. For such inputs, where the result has more than 2000 digits before the decimal point, the decimal result weight estimate was being clamped to 2000, leading to a loss of precision in the final calculation. Fix this by replacing the clamping code with an overflow test that aborts the calculation early if the final result is sure to overflow, based on the overflow limit in exp_var(). This provides the same protection against integer overflow in the subsequent result scale computation as the original clamping code, but it also ensures that precision is never lost and saves compute cycles in cases that are sure to overflow. The new early overflow test works with the initial low-precision result (expected to be accurate to around 8 significant digits) and includes a small fuzz factor to ensure that it doesn't kick in for values that would not overflow exp_var(), so the overall overflow threshold of pow() is unchanged and consistent for all inputs with non-integer exponents. Author: Dean Rasheed Reviewed-by: Tom Lane Discussion: http://www.postgresql.org/message-id/CAEZATCUj3U-cQj0jjoia=qgs0SjE3auroxh8swvNKvZWUqegrg@mail.gmail.com See-also: http://www.postgresql.org/message-id/CAEZATCV7w+8iB=07dJ8Q0zihXQT1semcQuTeK+4_rogC_zq5Hw@mail.gmail.com
2016-05-05 12:16:17 +02:00
/* If you change this limit, see also power_var()'s limit */
2015-11-14 20:55:38 +01:00
if (Abs(val) >= NUMERIC_MAX_RESULT_SCALE * 3)
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value overflows numeric format")));
2015-11-14 20:55:38 +01:00
/* decimal weight = log10(e^x) = x * log10(e) */
dweight = (int) (val * 0.434294481903252);
2015-11-14 20:55:38 +01:00
/*
* Reduce x to the range -0.01 <= x <= 0.01 (approximately) by dividing by
* 2^n, to improve the convergence rate of the Taylor series.
*/
if (Abs(val) > 0.01)
{
2015-11-14 20:55:38 +01:00
NumericVar tmp;
2015-11-14 20:55:38 +01:00
init_var(&tmp);
set_var_from_var(&const_two, &tmp);
2015-11-14 20:55:38 +01:00
ndiv2 = 1;
val /= 2;
2015-11-14 20:55:38 +01:00
while (Abs(val) > 0.01)
{
ndiv2++;
val /= 2;
add_var(&tmp, &tmp, &tmp);
}
2015-11-14 20:55:38 +01:00
local_rscale = x.dscale + ndiv2;
div_var_fast(&x, &tmp, &x, local_rscale, true);
2015-11-14 20:55:38 +01:00
free_var(&tmp);
}
else
ndiv2 = 0;
2015-11-14 20:55:38 +01:00
/*
* Set the scale for the Taylor series expansion. The final result has
* (dweight + rscale + 1) significant digits. In addition, we have to
* raise the Taylor series result to the power 2^ndiv2, which introduces
* an error of up to around log10(2^ndiv2) digits, so work with this many
* extra digits of precision (plus a few more for good measure).
*/
sig_digits = 1 + dweight + rscale + (int) (ndiv2 * 0.301029995663981);
sig_digits = Max(sig_digits, 0) + 8;
2015-11-14 20:55:38 +01:00
local_rscale = sig_digits - 1;
/*
* Use the Taylor series
*
2003-08-04 02:43:34 +02:00
* 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 local_rscale limit.
*/
add_var(&const_one, &x, result);
2015-11-14 20:55:38 +01:00
mul_var(&x, &x, &elem, local_rscale);
set_var_from_var(&const_two, &ni);
div_var_fast(&elem, &ni, &elem, local_rscale, true);
2015-11-14 20:55:38 +01:00
while (elem.ndigits != 0)
{
add_var(result, &elem, result);
2015-11-14 20:55:38 +01:00
mul_var(&elem, &x, &elem, local_rscale);
add_var(&ni, &const_one, &ni);
div_var_fast(&elem, &ni, &elem, local_rscale, true);
}
2015-11-14 20:55:38 +01:00
/*
* Compensate for the argument range reduction. Since the weight of the
* result doubles with each multiplication, we can reduce the local rscale
* as we proceed.
*/
while (ndiv2-- > 0)
2015-11-14 20:55:38 +01:00
{
local_rscale = sig_digits - result->weight * 2 * DEC_DIGITS;
local_rscale = Max(local_rscale, NUMERIC_MIN_DISPLAY_SCALE);
mul_var(result, result, result, local_rscale);
2015-11-14 20:55:38 +01:00
}
/* Round to requested rscale */
round_var(result, rscale);
free_var(&x);
free_var(&elem);
free_var(&ni);
}
2015-11-14 20:55:38 +01:00
/*
* Estimate the dweight of the most significant decimal digit of the natural
* logarithm of a number.
*
* Essentially, we're approximating log10(abs(ln(var))). This is used to
* determine the appropriate rscale when computing natural logarithms.
*/
static int
estimate_ln_dweight(const NumericVar *var)
2015-11-14 20:55:38 +01:00
{
int ln_dweight;
if (cmp_var(var, &const_zero_point_nine) >= 0 &&
cmp_var(var, &const_one_point_one) <= 0)
{
/*
* 0.9 <= var <= 1.1
*
* ln(var) has a negative weight (possibly very large). To get a
* reasonably accurate result, estimate it using ln(1+x) ~= x.
*/
NumericVar x;
init_var(&x);
sub_var(var, &const_one, &x);
if (x.ndigits > 0)
{
/* Use weight of most significant decimal digit of x */
ln_dweight = x.weight * DEC_DIGITS + (int) log10(x.digits[0]);
}
else
{
/* x = 0. Since ln(1) = 0 exactly, we don't need extra digits */
ln_dweight = 0;
}
free_var(&x);
}
else
{
/*
* Estimate the logarithm using the first couple of digits from the
* input number. This will give an accurate result whenever the input
* is not too close to 1.
*/
if (var->ndigits > 0)
{
int digits;
int dweight;
double ln_var;
digits = var->digits[0];
dweight = var->weight * DEC_DIGITS;
if (var->ndigits > 1)
{
digits = digits * NBASE + var->digits[1];
dweight -= DEC_DIGITS;
}
/*----------
* We have var ~= digits * 10^dweight
* so ln(var) ~= ln(digits) + dweight * ln(10)
*----------
*/
ln_var = log((double) digits) + dweight * 2.302585092994046;
ln_dweight = (int) log10(Abs(ln_var));
}
else
{
/* Caller should fail on ln(0), but for the moment return zero */
ln_dweight = 0;
}
}
return ln_dweight;
}
/*
* ln_var() -
*
* Compute the natural log of x
*/
static void
ln_var(const NumericVar *arg, NumericVar *result, int rscale)
{
1999-05-25 18:15:34 +02:00
NumericVar x;
NumericVar xx;
NumericVar ni;
NumericVar elem;
NumericVar fact;
int nsqrt;
int local_rscale;
int cmp;
cmp = cmp_var(arg, &const_zero);
if (cmp == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_LOG),
errmsg("cannot take logarithm of zero")));
else if (cmp < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_LOG),
errmsg("cannot take logarithm of a negative number")));
init_var(&x);
init_var(&xx);
init_var(&ni);
init_var(&elem);
init_var(&fact);
set_var_from_var(arg, &x);
set_var_from_var(&const_two, &fact);
2015-11-14 20:55:38 +01:00
/*
* Reduce input into range 0.9 < x < 1.1 with repeated sqrt() operations.
*
* The final logarithm will have up to around rscale+6 significant digits.
* Each sqrt() will roughly halve the weight of x, so adjust the local
* rscale as we work so that we keep this many significant digits at each
* step (plus a few more for good measure).
*
* Note that we allow local_rscale < 0 during this input reduction
* process, which implies rounding before the decimal point. sqrt_var()
* explicitly supports this, and it significantly reduces the work
* required to reduce very large inputs to the required range. Once the
* input reduction is complete, x.weight will be 0 and its display scale
* will be non-negative again.
2015-11-14 20:55:38 +01:00
*/
nsqrt = 0;
while (cmp_var(&x, &const_zero_point_nine) <= 0)
{
2015-11-14 20:55:38 +01:00
local_rscale = rscale - x.weight * DEC_DIGITS / 2 + 8;
sqrt_var(&x, &x, local_rscale);
mul_var(&fact, &const_two, &fact, 0);
nsqrt++;
}
while (cmp_var(&x, &const_one_point_one) >= 0)
{
2015-11-14 20:55:38 +01:00
local_rscale = rscale - x.weight * DEC_DIGITS / 2 + 8;
sqrt_var(&x, &x, local_rscale);
mul_var(&fact, &const_two, &fact, 0);
nsqrt++;
}
/*
* We use the Taylor series for 0.5 * ln((1+z)/(1-z)),
*
2003-08-04 02:43:34 +02:00
* 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.
*
* The Taylor series result will be multiplied by 2^(nsqrt+1), which has a
* decimal weight of (nsqrt+1) * log10(2), so work with this many extra
* digits of precision (plus a few more for good measure).
*/
local_rscale = rscale + (int) ((nsqrt + 1) * 0.301029995663981) + 8;
2015-11-14 20:55:38 +01:00
sub_var(&x, &const_one, result);
add_var(&x, &const_one, &elem);
div_var_fast(result, &elem, result, local_rscale, true);
set_var_from_var(result, &xx);
mul_var(result, result, &x, local_rscale);
set_var_from_var(&const_one, &ni);
for (;;)
{
add_var(&ni, &const_two, &ni);
mul_var(&xx, &x, &xx, local_rscale);
div_var_fast(&xx, &ni, &elem, local_rscale, true);
if (elem.ndigits == 0)
break;
add_var(result, &elem, result);
2003-08-04 02:43:34 +02:00
if (elem.weight < (result->weight - local_rscale * 2 / DEC_DIGITS))
break;
}
/* Compensate for argument range reduction, round to requested rscale */
mul_var(result, &fact, result, rscale);
free_var(&x);
free_var(&xx);
free_var(&ni);
free_var(&elem);
free_var(&fact);
}
/*
* log_var() -
*
* Compute the logarithm of num in a given base.
*
* Note: this routine chooses dscale of the result.
*/
static void
log_var(const NumericVar *base, const NumericVar *num, NumericVar *result)
{
NumericVar ln_base;
NumericVar ln_num;
2015-11-14 20:55:38 +01:00
int ln_base_dweight;
int ln_num_dweight;
int result_dweight;
int rscale;
2015-11-14 20:55:38 +01:00
int ln_base_rscale;
int ln_num_rscale;
init_var(&ln_base);
init_var(&ln_num);
2015-11-14 20:55:38 +01:00
/* Estimated dweights of ln(base), ln(num) and the final result */
ln_base_dweight = estimate_ln_dweight(base);
ln_num_dweight = estimate_ln_dweight(num);
result_dweight = ln_num_dweight - ln_base_dweight;
2015-11-14 20:55:38 +01:00
/*
* Select the scale of the result so that it will have at least
* NUMERIC_MIN_SIG_DIGITS significant digits and is not less than either
* input's display scale.
*/
rscale = NUMERIC_MIN_SIG_DIGITS - result_dweight;
rscale = Max(rscale, base->dscale);
rscale = Max(rscale, num->dscale);
rscale = Max(rscale, NUMERIC_MIN_DISPLAY_SCALE);
rscale = Min(rscale, NUMERIC_MAX_DISPLAY_SCALE);
2015-11-14 20:55:38 +01:00
/*
* Set the scales for ln(base) and ln(num) so that they each have more
* significant digits than the final result.
*/
ln_base_rscale = rscale + result_dweight - ln_base_dweight + 8;
ln_base_rscale = Max(ln_base_rscale, NUMERIC_MIN_DISPLAY_SCALE);
2015-11-14 20:55:38 +01:00
ln_num_rscale = rscale + result_dweight - ln_num_dweight + 8;
ln_num_rscale = Max(ln_num_rscale, NUMERIC_MIN_DISPLAY_SCALE);
2015-11-14 20:55:38 +01:00
/* Form natural logarithms */
ln_var(base, &ln_base, ln_base_rscale);
ln_var(num, &ln_num, ln_num_rscale);
1999-05-25 18:15:34 +02:00
2015-11-14 20:55:38 +01:00
/* Divide and round to the required scale */
div_var_fast(&ln_num, &ln_base, result, rscale, true);
free_var(&ln_num);
free_var(&ln_base);
}
/*
* power_var() -
*
* Raise base to the power of exp
*
* Note: this routine chooses dscale of the result.
*/
static void
power_var(const NumericVar *base, const NumericVar *exp, NumericVar *result)
{
NumericVar ln_base;
NumericVar ln_num;
2015-11-14 20:55:38 +01:00
int ln_dweight;
int rscale;
int local_rscale;
double val;
/* If exp can be represented as an integer, use power_var_int */
if (exp->ndigits == 0 || exp->ndigits <= exp->weight + 1)
{
/* exact integer, but does it fit in int? */
int64 expval64;
if (numericvar_to_int64(exp, &expval64))
{
2003-08-04 02:43:34 +02:00
int expval = (int) expval64;
/* Test for overflow by reverse-conversion. */
if ((int64) expval == expval64)
{
/* Okay, select rscale */
rscale = NUMERIC_MIN_SIG_DIGITS;
rscale = Max(rscale, base->dscale);
rscale = Max(rscale, NUMERIC_MIN_DISPLAY_SCALE);
rscale = Min(rscale, NUMERIC_MAX_DISPLAY_SCALE);
power_var_int(base, expval, result, rscale);
return;
}
}
}
/*
2015-11-14 20:55:38 +01:00
* This avoids log(0) for cases of 0 raised to a non-integer. 0 ^ 0 is
* handled by power_var_int().
*/
if (cmp_var(base, &const_zero) == 0)
{
set_var_from_var(&const_zero, result);
Phase 2 of pgindent updates. Change pg_bsd_indent to follow upstream rules for placement of comments to the right of code, and remove pgindent hack that caused comments following #endif to not obey the general rule. Commit e3860ffa4dd0dad0dd9eea4be9cc1412373a8c89 wasn't actually using the published version of pg_bsd_indent, but a hacked-up version that tried to minimize the amount of movement of comments to the right of code. The situation of interest is where such a comment has to be moved to the right of its default placement at column 33 because there's code there. BSD indent has always moved right in units of tab stops in such cases --- but in the previous incarnation, indent was working in 8-space tab stops, while now it knows we use 4-space tabs. So the net result is that in about half the cases, such comments are placed one tab stop left of before. This is better all around: it leaves more room on the line for comment text, and it means that in such cases the comment uniformly starts at the next 4-space tab stop after the code, rather than sometimes one and sometimes two tabs after. Also, ensure that comments following #endif are indented the same as comments following other preprocessor commands such as #else. That inconsistency turns out to have been self-inflicted damage from a poorly-thought-through post-indent "fixup" in pgindent. This patch is much less interesting than the first round of indent changes, but also bulkier, so I thought it best to separate the effects. Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:18:54 +02:00
result->dscale = NUMERIC_MIN_SIG_DIGITS; /* no need to round */
return;
}
init_var(&ln_base);
init_var(&ln_num);
2015-11-14 20:55:38 +01:00
/*----------
* Decide on the scale for the ln() calculation. For this we need an
* estimate of the weight of the result, which we obtain by doing an
* initial low-precision calculation of exp * ln(base).
*
* We want result = e ^ (exp * ln(base))
* so result dweight = log10(result) = exp * ln(base) * log10(e)
Fix corner-case loss of precision in numeric pow() calculation Commit 7d9a4737c268f61fb8800957631f12d3f13be218 greatly improved the accuracy of the numeric transcendental functions, however it failed to consider the case where the result from pow() is close to the overflow threshold, for example 0.12 ^ -2345.6. For such inputs, where the result has more than 2000 digits before the decimal point, the decimal result weight estimate was being clamped to 2000, leading to a loss of precision in the final calculation. Fix this by replacing the clamping code with an overflow test that aborts the calculation early if the final result is sure to overflow, based on the overflow limit in exp_var(). This provides the same protection against integer overflow in the subsequent result scale computation as the original clamping code, but it also ensures that precision is never lost and saves compute cycles in cases that are sure to overflow. The new early overflow test works with the initial low-precision result (expected to be accurate to around 8 significant digits) and includes a small fuzz factor to ensure that it doesn't kick in for values that would not overflow exp_var(), so the overall overflow threshold of pow() is unchanged and consistent for all inputs with non-integer exponents. Author: Dean Rasheed Reviewed-by: Tom Lane Discussion: http://www.postgresql.org/message-id/CAEZATCUj3U-cQj0jjoia=qgs0SjE3auroxh8swvNKvZWUqegrg@mail.gmail.com See-also: http://www.postgresql.org/message-id/CAEZATCV7w+8iB=07dJ8Q0zihXQT1semcQuTeK+4_rogC_zq5Hw@mail.gmail.com
2016-05-05 12:16:17 +02:00
*
* We also perform a crude overflow test here so that we can exit early if
* the full-precision result is sure to overflow, and to guard against
* integer overflow when determining the scale for the real calculation.
* exp_var() supports inputs up to NUMERIC_MAX_RESULT_SCALE * 3, so the
* result will overflow if exp * ln(base) >= NUMERIC_MAX_RESULT_SCALE * 3.
* Since the values here are only approximations, we apply a small fuzz
* factor to this overflow test and let exp_var() determine the exact
* overflow threshold so that it is consistent for all inputs.
2015-11-14 20:55:38 +01:00
*----------
*/
ln_dweight = estimate_ln_dweight(base);
2015-11-14 20:55:38 +01:00
local_rscale = 8 - ln_dweight;
local_rscale = Max(local_rscale, NUMERIC_MIN_DISPLAY_SCALE);
local_rscale = Min(local_rscale, NUMERIC_MAX_DISPLAY_SCALE);
ln_var(base, &ln_base, local_rscale);
mul_var(&ln_base, exp, &ln_num, local_rscale);
val = numericvar_to_double_no_overflow(&ln_num);
Fix corner-case loss of precision in numeric pow() calculation Commit 7d9a4737c268f61fb8800957631f12d3f13be218 greatly improved the accuracy of the numeric transcendental functions, however it failed to consider the case where the result from pow() is close to the overflow threshold, for example 0.12 ^ -2345.6. For such inputs, where the result has more than 2000 digits before the decimal point, the decimal result weight estimate was being clamped to 2000, leading to a loss of precision in the final calculation. Fix this by replacing the clamping code with an overflow test that aborts the calculation early if the final result is sure to overflow, based on the overflow limit in exp_var(). This provides the same protection against integer overflow in the subsequent result scale computation as the original clamping code, but it also ensures that precision is never lost and saves compute cycles in cases that are sure to overflow. The new early overflow test works with the initial low-precision result (expected to be accurate to around 8 significant digits) and includes a small fuzz factor to ensure that it doesn't kick in for values that would not overflow exp_var(), so the overall overflow threshold of pow() is unchanged and consistent for all inputs with non-integer exponents. Author: Dean Rasheed Reviewed-by: Tom Lane Discussion: http://www.postgresql.org/message-id/CAEZATCUj3U-cQj0jjoia=qgs0SjE3auroxh8swvNKvZWUqegrg@mail.gmail.com See-also: http://www.postgresql.org/message-id/CAEZATCV7w+8iB=07dJ8Q0zihXQT1semcQuTeK+4_rogC_zq5Hw@mail.gmail.com
2016-05-05 12:16:17 +02:00
/* initial overflow test with fuzz factor */
if (Abs(val) > NUMERIC_MAX_RESULT_SCALE * 3.01)
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value overflows numeric format")));
Fix corner-case loss of precision in numeric pow() calculation Commit 7d9a4737c268f61fb8800957631f12d3f13be218 greatly improved the accuracy of the numeric transcendental functions, however it failed to consider the case where the result from pow() is close to the overflow threshold, for example 0.12 ^ -2345.6. For such inputs, where the result has more than 2000 digits before the decimal point, the decimal result weight estimate was being clamped to 2000, leading to a loss of precision in the final calculation. Fix this by replacing the clamping code with an overflow test that aborts the calculation early if the final result is sure to overflow, based on the overflow limit in exp_var(). This provides the same protection against integer overflow in the subsequent result scale computation as the original clamping code, but it also ensures that precision is never lost and saves compute cycles in cases that are sure to overflow. The new early overflow test works with the initial low-precision result (expected to be accurate to around 8 significant digits) and includes a small fuzz factor to ensure that it doesn't kick in for values that would not overflow exp_var(), so the overall overflow threshold of pow() is unchanged and consistent for all inputs with non-integer exponents. Author: Dean Rasheed Reviewed-by: Tom Lane Discussion: http://www.postgresql.org/message-id/CAEZATCUj3U-cQj0jjoia=qgs0SjE3auroxh8swvNKvZWUqegrg@mail.gmail.com See-also: http://www.postgresql.org/message-id/CAEZATCV7w+8iB=07dJ8Q0zihXQT1semcQuTeK+4_rogC_zq5Hw@mail.gmail.com
2016-05-05 12:16:17 +02:00
val *= 0.434294481903252; /* approximate decimal result weight */
2015-11-14 20:55:38 +01:00
/* choose the result scale */
rscale = NUMERIC_MIN_SIG_DIGITS - (int) val;
rscale = Max(rscale, base->dscale);
rscale = Max(rscale, exp->dscale);
rscale = Max(rscale, NUMERIC_MIN_DISPLAY_SCALE);
rscale = Min(rscale, NUMERIC_MAX_DISPLAY_SCALE);
2015-11-14 20:55:38 +01:00
/* set the scale for the real exp * ln(base) calculation */
local_rscale = rscale + (int) val - ln_dweight + 8;
local_rscale = Max(local_rscale, NUMERIC_MIN_DISPLAY_SCALE);
/* and do the real calculation */
ln_var(base, &ln_base, local_rscale);
mul_var(&ln_base, exp, &ln_num, local_rscale);
exp_var(&ln_num, result, rscale);
free_var(&ln_num);
free_var(&ln_base);
}
1999-05-25 18:15:34 +02:00
/*
* power_var_int() -
*
* Raise base to the power of exp, where exp is an integer.
*/
static void
power_var_int(const NumericVar *base, int exp, NumericVar *result, int rscale)
{
2015-11-14 20:55:38 +01:00
double f;
int p;
int i;
int sig_digits;
unsigned int mask;
bool neg;
NumericVar base_prod;
int local_rscale;
/* Handle some common special cases, as well as corner cases */
switch (exp)
{
case 0:
/*
* While 0 ^ 0 can be either 1 or indeterminate (error), we treat
* it as 1 because most programming languages do this. SQL:2003
* also requires a return value of 1.
* https://en.wikipedia.org/wiki/Exponentiation#Zero_to_the_zero_power
*/
set_var_from_var(&const_one, result);
2003-08-04 02:43:34 +02:00
result->dscale = rscale; /* no need to round */
return;
case 1:
set_var_from_var(base, result);
round_var(result, rscale);
return;
case -1:
div_var(&const_one, base, result, rscale, true);
return;
case 2:
mul_var(base, base, result, rscale);
return;
default:
break;
}
2015-11-14 20:55:38 +01:00
/* Handle the special case where the base is zero */
if (base->ndigits == 0)
{
if (exp < 0)
ereport(ERROR,
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
zero_var(result);
result->dscale = rscale;
return;
}
/*
2003-08-04 02:43:34 +02:00
* The general case repeatedly multiplies base according to the bit
2015-11-14 20:55:38 +01:00
* pattern of exp.
*
* First we need to estimate the weight of the result so that we know how
* many significant digits are needed.
*/
f = base->digits[0];
p = base->weight * DEC_DIGITS;
for (i = 1; i < base->ndigits && i * DEC_DIGITS < 16; i++)
{
f = f * NBASE + base->digits[i];
p -= DEC_DIGITS;
}
/*----------
* We have base ~= f * 10^p
* so log10(result) = log10(base^exp) ~= exp * (log10(f) + p)
*----------
*/
f = exp * (log10(f) + p);
/*
* Apply crude overflow/underflow tests so we can exit early if the result
* certainly will overflow/underflow.
*/
if (f > 3 * SHRT_MAX * DEC_DIGITS)
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value overflows numeric format")));
if (f + 1 < -rscale || f + 1 < -NUMERIC_MAX_DISPLAY_SCALE)
{
zero_var(result);
result->dscale = rscale;
return;
}
/*
* Approximate number of significant digits in the result. Note that the
* underflow test above means that this is necessarily >= 0.
*/
sig_digits = 1 + rscale + (int) f;
/*
* The multiplications to produce the result may introduce an error of up
* to around log10(abs(exp)) digits, so work with this many extra digits
* of precision (plus a few more for good measure).
*/
sig_digits += (int) log(Abs(exp)) + 8;
/*
* Now we can proceed with the multiplications.
*/
neg = (exp < 0);
mask = Abs(exp);
init_var(&base_prod);
set_var_from_var(base, &base_prod);
if (mask & 1)
set_var_from_var(base, result);
else
set_var_from_var(&const_one, result);
while ((mask >>= 1) > 0)
{
2015-11-14 20:55:38 +01:00
/*
* Do the multiplications using rscales large enough to hold the
* results to the required number of significant digits, but don't
* waste time by exceeding the scales of the numbers themselves.
*/
local_rscale = sig_digits - 2 * base_prod.weight * DEC_DIGITS;
local_rscale = Min(local_rscale, 2 * base_prod.dscale);
local_rscale = Max(local_rscale, NUMERIC_MIN_DISPLAY_SCALE);
mul_var(&base_prod, &base_prod, &base_prod, local_rscale);
2015-11-14 20:55:38 +01:00
if (mask & 1)
2015-11-14 20:55:38 +01:00
{
local_rscale = sig_digits -
(base_prod.weight + result->weight) * DEC_DIGITS;
local_rscale = Min(local_rscale,
base_prod.dscale + result->dscale);
local_rscale = Max(local_rscale, NUMERIC_MIN_DISPLAY_SCALE);
mul_var(&base_prod, result, result, local_rscale);
2015-11-14 20:55:38 +01:00
}
/*
* When abs(base) > 1, the number of digits to the left of the decimal
* point in base_prod doubles at each iteration, so if exp is large we
* could easily spend large amounts of time and memory space doing the
* multiplications. But once the weight exceeds what will fit in
* int16, the final result is guaranteed to overflow (or underflow, if
* exp < 0), so we can give up before wasting too many cycles.
*/
if (base_prod.weight > SHRT_MAX || result->weight > SHRT_MAX)
{
/* overflow, unless neg, in which case result should be 0 */
if (!neg)
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value overflows numeric format")));
zero_var(result);
neg = false;
break;
}
}
free_var(&base_prod);
/* Compensate for input sign, and round to requested rscale */
if (neg)
div_var_fast(&const_one, result, result, rscale, true);
else
round_var(result, rscale);
}
/* ----------------------------------------------------------------------
*
* Following are the lowest level functions that operate unsigned
* on the variable level
*
* ----------------------------------------------------------------------
*/
/* ----------
* cmp_abs() -
*
* Compare the absolute values of var1 and var2
* Returns: -1 for ABS(var1) < ABS(var2)
* 0 for ABS(var1) == ABS(var2)
* 1 for ABS(var1) > ABS(var2)
* ----------
*/
static int
cmp_abs(const NumericVar *var1, const NumericVar *var2)
{
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
return cmp_abs_common(var1->digits, var1->ndigits, var1->weight,
var2->digits, var2->ndigits, var2->weight);
}
/* ----------
* cmp_abs_common() -
*
2006-10-04 02:30:14 +02:00
* Main routine of cmp_abs(). This function can be used by both
* NumericVar and Numeric.
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
* ----------
*/
static int
cmp_abs_common(const NumericDigit *var1digits, int var1ndigits, int var1weight,
const NumericDigit *var2digits, int var2ndigits, int var2weight)
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
{
1999-05-25 18:15:34 +02:00
int i1 = 0;
int i2 = 0;
/* Check any digits before the first common digit */
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
while (var1weight > var2weight && i1 < var1ndigits)
{
if (var1digits[i1++] != 0)
1999-05-25 18:15:34 +02:00
return 1;
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
var1weight--;
}
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
while (var2weight > var1weight && i2 < var2ndigits)
{
if (var2digits[i2++] != 0)
1999-05-25 18:15:34 +02:00
return -1;
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
var2weight--;
}
/* At this point, either w1 == w2 or we've run out of digits */
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
if (var1weight == var2weight)
{
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
while (i1 < var1ndigits && i2 < var2ndigits)
{
int stat = var1digits[i1++] - var2digits[i2++];
if (stat)
{
if (stat > 0)
return 1;
return -1;
}
}
}
/*
2005-10-15 04:49:52 +02:00
* At this point, we've run out of digits on one side or the other; so any
* remaining nonzero digits imply that side is larger
*/
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
while (i1 < var1ndigits)
{
if (var1digits[i1++] != 0)
return 1;
}
I think that NUMERIC datatype has a problem in the performance that the format on Tuple(Numeric) and the format to calculate(NumericVar) are different. I understood that to reduce I/O. However, when many comparisons or calculations of NUMERIC are executed, the conversion of Numeric and NumericVar becomes a bottleneck. It is profile result when "create index on NUMERIC column" is executed: % cumulative self self total time seconds seconds calls s/call s/call name 17.61 10.27 10.27 34542006 0.00 0.00 cmp_numerics 11.90 17.21 6.94 34542006 0.00 0.00 comparetup_index 7.42 21.54 4.33 71102587 0.00 0.00 AllocSetAlloc 7.02 25.64 4.09 69084012 0.00 0.00 set_var_from_num 4.87 28.48 2.84 69084012 0.00 0.00 alloc_var 4.79 31.27 2.79 142205745 0.00 0.00 AllocSetFreeIndex 4.55 33.92 2.65 34542004 0.00 0.00 cmp_abs 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s 4.07 36.30 2.38 71101189 0.00 0.00 AllocSetFree 3.83 38.53 2.23 69084012 0.00 0.00 free_var The create index command executes many comparisons of Numeric values. Functions other than comparetup_index spent a lot of cycles for conversion from Numeric to NumericVar. An attached patch enables the comparison of Numeric values without executing conversion to NumericVar. The execution time of that SQL becomes half. o Test SQL (index_test table has 1,000,000 tuples) create index index_test_idx on index_test(num_col); o Test results (executed the test five times) (1)PentiumIII original: 39.789s 36.823s 36.737s 37.752s 37.019s patched : 18.560s 19.103s 18.830s 18.408s 18.853s (2)Pentium4 original: 16.349s 14.997s 12.979s 13.169s 12.955s patched : 7.005s 6.594s 6.770s 6.740s 6.828s (3)Itanium2 original: 15.392s 15.447s 15.350s 15.370s 15.417s patched : 7.413s 7.330s 7.334s 7.339s 7.339s (4)Ultra Sparc original: 64.435s 59.336s 59.332s 58.455s 59.781s patched : 28.630s 28.666s 28.983s 28.744s 28.595s Atsushi Ogawa
2006-02-07 17:03:50 +01:00
while (i2 < var2ndigits)
{
if (var2digits[i2++] != 0)
return -1;
}
return 0;
}
/*
* add_abs() -
*
* Add the absolute values of two variables into result.
* result might point to one of the operands without danger.
*/
static void
add_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
{
NumericDigit *res_buf;
1999-05-25 18:15:34 +02:00
NumericDigit *res_digits;
int res_ndigits;
int res_weight;
int res_rscale,
rscale1,
rscale2;
1999-05-25 18:15:34 +02:00
int res_dscale;
int i,
i1,
i2;
int carry = 0;
2001-03-22 05:01:46 +01:00
/* copy these values into local vars for speed in inner loop */
int var1ndigits = var1->ndigits;
int var2ndigits = var2->ndigits;
NumericDigit *var1digits = var1->digits;
NumericDigit *var2digits = var2->digits;
res_weight = Max(var1->weight, var2->weight) + 1;
res_dscale = Max(var1->dscale, var2->dscale);
/* Note: here we are figuring rscale in base-NBASE digits */
rscale1 = var1->ndigits - var1->weight - 1;
rscale2 = var2->ndigits - var2->weight - 1;
res_rscale = Max(rscale1, rscale2);
res_ndigits = res_rscale + res_weight + 1;
if (res_ndigits <= 0)
res_ndigits = 1;
res_buf = digitbuf_alloc(res_ndigits + 1);
res_buf[0] = 0; /* spare digit for later rounding */
res_digits = res_buf + 1;
i1 = res_rscale + var1->weight + 1;
i2 = res_rscale + var2->weight + 1;
for (i = res_ndigits - 1; i >= 0; i--)
{
i1--;
i2--;
if (i1 >= 0 && i1 < var1ndigits)
carry += var1digits[i1];
if (i2 >= 0 && i2 < var2ndigits)
carry += var2digits[i2];
if (carry >= NBASE)
{
res_digits[i] = carry - NBASE;
carry = 1;
}
else
{
res_digits[i] = carry;
carry = 0;
}
}
Assert(carry == 0); /* else we failed to allow for carry out */
digitbuf_free(result->buf);
result->ndigits = res_ndigits;
1999-05-25 18:15:34 +02:00
result->buf = res_buf;
result->digits = res_digits;
result->weight = res_weight;
result->dscale = res_dscale;
/* Remove leading/trailing zeroes */
strip_var(result);
}
/*
* sub_abs()
*
* Subtract the absolute value of var2 from the absolute value of var1
* and store in result. result might point to one of the operands
* without danger.
*
* ABS(var1) MUST BE GREATER OR EQUAL ABS(var2) !!!
*/
static void
sub_abs(const NumericVar *var1, const NumericVar *var2, NumericVar *result)
{
NumericDigit *res_buf;
1999-05-25 18:15:34 +02:00
NumericDigit *res_digits;
int res_ndigits;
int res_weight;
int res_rscale,
rscale1,
rscale2;
1999-05-25 18:15:34 +02:00
int res_dscale;
int i,
i1,
i2;
int borrow = 0;
2001-03-22 05:01:46 +01:00
/* copy these values into local vars for speed in inner loop */
int var1ndigits = var1->ndigits;
int var2ndigits = var2->ndigits;
NumericDigit *var1digits = var1->digits;
NumericDigit *var2digits = var2->digits;
res_weight = var1->weight;
res_dscale = Max(var1->dscale, var2->dscale);
/* Note: here we are figuring rscale in base-NBASE digits */
rscale1 = var1->ndigits - var1->weight - 1;
rscale2 = var2->ndigits - var2->weight - 1;
res_rscale = Max(rscale1, rscale2);
res_ndigits = res_rscale + res_weight + 1;
if (res_ndigits <= 0)
res_ndigits = 1;
res_buf = digitbuf_alloc(res_ndigits + 1);
res_buf[0] = 0; /* spare digit for later rounding */
res_digits = res_buf + 1;
i1 = res_rscale + var1->weight + 1;
i2 = res_rscale + var2->weight + 1;
for (i = res_ndigits - 1; i >= 0; i--)
{
i1--;
i2--;
if (i1 >= 0 && i1 < var1ndigits)
borrow += var1digits[i1];
if (i2 >= 0 && i2 < var2ndigits)
borrow -= var2digits[i2];
if (borrow < 0)
{
res_digits[i] = borrow + NBASE;
borrow = -1;
}
else
{
res_digits[i] = borrow;
borrow = 0;
}
}
Assert(borrow == 0); /* else caller gave us var1 < var2 */
digitbuf_free(result->buf);
result->ndigits = res_ndigits;
1999-05-25 18:15:34 +02:00
result->buf = res_buf;
result->digits = res_digits;
result->weight = res_weight;
result->dscale = res_dscale;
/* Remove leading/trailing zeroes */
strip_var(result);
}
/*
* round_var
*
* Round the value of a variable to no more than rscale decimal digits
* after the decimal point. NOTE: we allow rscale < 0 here, implying
* rounding before the decimal point.
*/
static void
round_var(NumericVar *var, int rscale)
{
2003-08-04 02:43:34 +02:00
NumericDigit *digits = var->digits;
int di;
int ndigits;
int carry;
var->dscale = rscale;
/* decimal digits wanted */
di = (var->weight + 1) * DEC_DIGITS + rscale;
/*
2005-10-15 04:49:52 +02:00
* If di = 0, the value loses all digits, but could round up to 1 if its
* first extra digit is >= 5. If di < 0 the result must be 0.
*/
if (di < 0)
{
var->ndigits = 0;
var->weight = 0;
var->sign = NUMERIC_POS;
}
else
{
/* NBASE digits wanted */
2003-08-04 02:43:34 +02:00
ndigits = (di + DEC_DIGITS - 1) / DEC_DIGITS;
/* 0, or number of decimal digits to keep in last NBASE digit */
di %= DEC_DIGITS;
if (ndigits < var->ndigits ||
(ndigits == var->ndigits && di > 0))
{
var->ndigits = ndigits;
#if DEC_DIGITS == 1
/* di must be zero */
carry = (digits[ndigits] >= HALF_NBASE) ? 1 : 0;
#else
if (di == 0)
carry = (digits[ndigits] >= HALF_NBASE) ? 1 : 0;
else
{
/* Must round within last NBASE digit */
2003-08-04 02:43:34 +02:00
int extra,
pow10;
#if DEC_DIGITS == 4
pow10 = round_powers[di];
#elif DEC_DIGITS == 2
pow10 = 10;
#else
#error unsupported NBASE
#endif
extra = digits[--ndigits] % pow10;
digits[ndigits] -= extra;
carry = 0;
2003-08-04 02:43:34 +02:00
if (extra >= pow10 / 2)
{
pow10 += digits[ndigits];
if (pow10 >= NBASE)
{
pow10 -= NBASE;
carry = 1;
}
digits[ndigits] = pow10;
}
}
#endif
/* Propagate carry if needed */
while (carry)
{
carry += digits[--ndigits];
if (carry >= NBASE)
{
digits[ndigits] = carry - NBASE;
carry = 1;
}
else
{
digits[ndigits] = carry;
carry = 0;
}
}
if (ndigits < 0)
{
Assert(ndigits == -1); /* better not have added > 1 digit */
Assert(var->digits > var->buf);
var->digits--;
var->ndigits++;
var->weight++;
}
}
}
}
/*
* trunc_var
*
* Truncate (towards zero) the value of a variable at rscale decimal digits
* after the decimal point. NOTE: we allow rscale < 0 here, implying
* truncation before the decimal point.
*/
static void
trunc_var(NumericVar *var, int rscale)
{
int di;
int ndigits;
var->dscale = rscale;
/* decimal digits wanted */
di = (var->weight + 1) * DEC_DIGITS + rscale;
/*
* If di <= 0, the value loses all digits.
*/
if (di <= 0)
{
var->ndigits = 0;
var->weight = 0;
var->sign = NUMERIC_POS;
}
else
{
/* NBASE digits wanted */
2003-08-04 02:43:34 +02:00
ndigits = (di + DEC_DIGITS - 1) / DEC_DIGITS;
if (ndigits <= var->ndigits)
{
var->ndigits = ndigits;
#if DEC_DIGITS == 1
/* no within-digit stuff to worry about */
#else
/* 0, or number of decimal digits to keep in last NBASE digit */
di %= DEC_DIGITS;
if (di > 0)
{
/* Must truncate within last NBASE digit */
2003-08-04 02:43:34 +02:00
NumericDigit *digits = var->digits;
int extra,
pow10;
#if DEC_DIGITS == 4
pow10 = round_powers[di];
#elif DEC_DIGITS == 2
pow10 = 10;
#else
#error unsupported NBASE
#endif
extra = digits[--ndigits] % pow10;
digits[ndigits] -= extra;
}
#endif
}
}
}
/*
* strip_var
*
* Strip any leading and trailing zeroes from a numeric variable
*/
static void
strip_var(NumericVar *var)
{
2003-08-04 02:43:34 +02:00
NumericDigit *digits = var->digits;
int ndigits = var->ndigits;
/* Strip leading zeroes */
while (ndigits > 0 && *digits == 0)
{
digits++;
var->weight--;
ndigits--;
}
/* Strip trailing zeroes */
while (ndigits > 0 && digits[ndigits - 1] == 0)
ndigits--;
/* If it's zero, normalize the sign and weight */
if (ndigits == 0)
{
var->sign = NUMERIC_POS;
var->weight = 0;
}
var->digits = digits;
var->ndigits = ndigits;
}
/* ----------------------------------------------------------------------
*
* Fast sum accumulator functions
*
* ----------------------------------------------------------------------
*/
/*
* Reset the accumulator's value to zero. The buffers to hold the digits
* are not free'd.
*/
static void
accum_sum_reset(NumericSumAccum *accum)
{
int i;
accum->dscale = 0;
for (i = 0; i < accum->ndigits; i++)
{
accum->pos_digits[i] = 0;
accum->neg_digits[i] = 0;
}
}
/*
* Accumulate a new value.
*/
static void
accum_sum_add(NumericSumAccum *accum, const NumericVar *val)
{
int32 *accum_digits;
int i,
val_i;
int val_ndigits;
NumericDigit *val_digits;
/*
* If we have accumulated too many values since the last carry
* propagation, do it now, to avoid overflowing. (We could allow more
* than NBASE - 1, if we reserved two extra digits, rather than one, for
* carry propagation. But even with NBASE - 1, this needs to be done so
* seldom, that the performance difference is negligible.)
*/
if (accum->num_uncarried == NBASE - 1)
accum_sum_carry(accum);
/*
* Adjust the weight or scale of the old value, so that it can accommodate
* the new value.
*/
accum_sum_rescale(accum, val);
/* */
if (val->sign == NUMERIC_POS)
accum_digits = accum->pos_digits;
else
accum_digits = accum->neg_digits;
/* copy these values into local vars for speed in loop */
val_ndigits = val->ndigits;
val_digits = val->digits;
i = accum->weight - val->weight;
for (val_i = 0; val_i < val_ndigits; val_i++)
{
accum_digits[i] += (int32) val_digits[val_i];
i++;
}
accum->num_uncarried++;
}
/*
* Propagate carries.
*/
static void
accum_sum_carry(NumericSumAccum *accum)
{
int i;
int ndigits;
int32 *dig;
int32 carry;
int32 newdig = 0;
/*
* If no new values have been added since last carry propagation, nothing
* to do.
*/
if (accum->num_uncarried == 0)
return;
/*
* We maintain that the weight of the accumulator is always one larger
* than needed to hold the current value, before carrying, to make sure
* there is enough space for the possible extra digit when carry is
* propagated. We cannot expand the buffer here, unless we require
* callers of accum_sum_final() to switch to the right memory context.
*/
Assert(accum->pos_digits[0] == 0 && accum->neg_digits[0] == 0);
ndigits = accum->ndigits;
/* Propagate carry in the positive sum */
dig = accum->pos_digits;
carry = 0;
for (i = ndigits - 1; i >= 0; i--)
{
newdig = dig[i] + carry;
if (newdig >= NBASE)
{
carry = newdig / NBASE;
newdig -= carry * NBASE;
}
else
carry = 0;
dig[i] = newdig;
}
/* Did we use up the digit reserved for carry propagation? */
if (newdig > 0)
accum->have_carry_space = false;
/* And the same for the negative sum */
dig = accum->neg_digits;
carry = 0;
for (i = ndigits - 1; i >= 0; i--)
{
newdig = dig[i] + carry;
if (newdig >= NBASE)
{
carry = newdig / NBASE;
newdig -= carry * NBASE;
}
else
carry = 0;
dig[i] = newdig;
}
if (newdig > 0)
accum->have_carry_space = false;
accum->num_uncarried = 0;
}
/*
* Re-scale accumulator to accommodate new value.
*
* If the new value has more digits than the current digit buffers in the
* accumulator, enlarge the buffers.
*/
static void
accum_sum_rescale(NumericSumAccum *accum, const NumericVar *val)
{
int old_weight = accum->weight;
int old_ndigits = accum->ndigits;
int accum_ndigits;
int accum_weight;
int accum_rscale;
int val_rscale;
accum_weight = old_weight;
accum_ndigits = old_ndigits;
/*
* Does the new value have a larger weight? If so, enlarge the buffers,
* and shift the existing value to the new weight, by adding leading
* zeros.
*
* We enforce that the accumulator always has a weight one larger than
* needed for the inputs, so that we have space for an extra digit at the
* final carry-propagation phase, if necessary.
*/
if (val->weight >= accum_weight)
{
accum_weight = val->weight + 1;
accum_ndigits = accum_ndigits + (accum_weight - old_weight);
}
/*
* Even though the new value is small, we might've used up the space
* reserved for the carry digit in the last call to accum_sum_carry(). If
* so, enlarge to make room for another one.
*/
else if (!accum->have_carry_space)
{
accum_weight++;
accum_ndigits++;
}
/* Is the new value wider on the right side? */
accum_rscale = accum_ndigits - accum_weight - 1;
val_rscale = val->ndigits - val->weight - 1;
if (val_rscale > accum_rscale)
accum_ndigits = accum_ndigits + (val_rscale - accum_rscale);
if (accum_ndigits != old_ndigits ||
accum_weight != old_weight)
{
int32 *new_pos_digits;
int32 *new_neg_digits;
int weightdiff;
weightdiff = accum_weight - old_weight;
new_pos_digits = palloc0(accum_ndigits * sizeof(int32));
new_neg_digits = palloc0(accum_ndigits * sizeof(int32));
if (accum->pos_digits)
{
memcpy(&new_pos_digits[weightdiff], accum->pos_digits,
old_ndigits * sizeof(int32));
pfree(accum->pos_digits);
memcpy(&new_neg_digits[weightdiff], accum->neg_digits,
old_ndigits * sizeof(int32));
pfree(accum->neg_digits);
}
accum->pos_digits = new_pos_digits;
accum->neg_digits = new_neg_digits;
accum->weight = accum_weight;
accum->ndigits = accum_ndigits;
Assert(accum->pos_digits[0] == 0 && accum->neg_digits[0] == 0);
accum->have_carry_space = true;
}
if (val->dscale > accum->dscale)
accum->dscale = val->dscale;
}
/*
* Return the current value of the accumulator. This perform final carry
* propagation, and adds together the positive and negative sums.
*
* Unlike all the other routines, the caller is not required to switch to
* the memory context that holds the accumulator.
*/
static void
accum_sum_final(NumericSumAccum *accum, NumericVar *result)
{
int i;
NumericVar pos_var;
NumericVar neg_var;
if (accum->ndigits == 0)
{
set_var_from_var(&const_zero, result);
return;
}
/* Perform final carry */
accum_sum_carry(accum);
/* Create NumericVars representing the positive and negative sums */
init_var(&pos_var);
init_var(&neg_var);
pos_var.ndigits = neg_var.ndigits = accum->ndigits;
pos_var.weight = neg_var.weight = accum->weight;
pos_var.dscale = neg_var.dscale = accum->dscale;
pos_var.sign = NUMERIC_POS;
neg_var.sign = NUMERIC_NEG;
pos_var.buf = pos_var.digits = digitbuf_alloc(accum->ndigits);
neg_var.buf = neg_var.digits = digitbuf_alloc(accum->ndigits);
for (i = 0; i < accum->ndigits; i++)
{
Assert(accum->pos_digits[i] < NBASE);
pos_var.digits[i] = (int16) accum->pos_digits[i];
Assert(accum->neg_digits[i] < NBASE);
neg_var.digits[i] = (int16) accum->neg_digits[i];
}
/* And add them together */
add_var(&pos_var, &neg_var, result);
/* Remove leading/trailing zeroes */
strip_var(result);
}
/*
* Copy an accumulator's state.
*
* 'dst' is assumed to be uninitialized beforehand. No attempt is made at
* freeing old values.
*/
static void
accum_sum_copy(NumericSumAccum *dst, NumericSumAccum *src)
{
dst->pos_digits = palloc(src->ndigits * sizeof(int32));
dst->neg_digits = palloc(src->ndigits * sizeof(int32));
memcpy(dst->pos_digits, src->pos_digits, src->ndigits * sizeof(int32));
memcpy(dst->neg_digits, src->neg_digits, src->ndigits * sizeof(int32));
dst->num_uncarried = src->num_uncarried;
dst->ndigits = src->ndigits;
dst->weight = src->weight;
dst->dscale = src->dscale;
}
/*
* Add the current value of 'accum2' into 'accum'.
*/
static void
accum_sum_combine(NumericSumAccum *accum, NumericSumAccum *accum2)
{
NumericVar tmp_var;
init_var(&tmp_var);
accum_sum_final(accum2, &tmp_var);
accum_sum_add(accum, &tmp_var);
free_var(&tmp_var);
}