/* ------------------------------------------------------------------------- * * numeric.h * Definitions for the exact numeric data type of openGauss * * Original coding 1998, Jan Wieck. Heavily revised 2003, Tom Lane. * * Copyright (c) 1998-2012, PostgreSQL Global Development Group * Portions Copyright (c) 2021, openGauss Contributors * * src/include/utils/numeric.h * * ------------------------------------------------------------------------- */ #ifndef _PG_NUMERIC_H_ #define _PG_NUMERIC_H_ #include "fmgr.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 1 #define NBASE 10000 #define HALF_NBASE 5000 #define DEC_DIGITS 4 /* decimal digits per NBASE digit */ #define NUMERIC_SCALE_ADJUST(scale) ((int64)(scale + DEC_DIGITS - 1) / DEC_DIGITS) #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[1]; /* Digits */ }; struct NumericLong { uint16 n_sign_dscale; /* Sign + display scale */ int16 n_weight; /* Weight of 1st digit */ NumericDigit n_data[1]; /* Digits */ }; /* * NumericBi is used for big integer(bi64 or bi128) * n_header stores mark bits and scale of big integer, first 4 bits to * distinguish bi64 and bi128, next 4 bits are not used, the last 8 bits * store the scale of bit integer, scale value is between 0 and 38. * n_data store big integer value(int64 or int128) * */ struct NumericBi { uint16 n_header; uint8 n_data[1]; }; /* * Add NumericBi struct to NumericChoice */ union NumericChoice { uint16 n_header; /* Header word */ struct NumericLong n_long; /* Long form (4-byte header) */ struct NumericShort n_short; /* Short form (2-byte header) */ struct NumericBi n_bi; /* Short form (2-byte header) */ }; struct NumericData { int32 vl_len_; /* varlena header (do not touch directly!) */ union NumericChoice choice; /* choice of format */ }; /* The actual contents of Numeric are private to numeric.c */ struct NumericData; typedef struct NumericData* Numeric; /* * 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 /* * big integer macro * n_header is the mark bits of numeric struct, when numeric is NAN, n_header * marked 0xC000. To distinguish bi64, bi128 and numeric, we use 0xD000 to mark * bi64, 0xE000 marks bi128, others are numeric type. */ #define NUMERIC_64 0xD000 #define NUMERIC_128 0xE000 #define NUMERIC_BI_MASK 0xF000 #define NUMERIC_BI_SCALEMASK 0x00FF /* * Hardcoded precision limit - arbitrary, but must be small enough that * dscale values will fit in 14 bits. */ #define NUMERIC_MAX_PRECISION 1000 #define NUMERIC_MAX_SCALE 1000 #define NUMERIC_MIN_SCALE -84 #define IS_FLOAT_AS_NUMERIC(scale) ((scale) == INT16_MIN) #define NUMERIC_TYPEMOD_MASK 0xffff /* * Internal limits on the scales chosen for calculation results */ #define NUMERIC_MAX_DISPLAY_SCALE NUMERIC_MAX_PRECISION #define NUMERIC_MIN_DISPLAY_SCALE 0 #define NUMERIC_MAX_RESULT_SCALE (NUMERIC_MAX_PRECISION * 2) /* * For inherently inexact calculations such as division and square root, * we try to get at least this many significant digits; the idea is to * deliver a result no worse than float8 would. */ #define NUMERIC_MIN_SIG_DIGITS 16 /* * numeric nan data length exclude header */ #define NUMERIC_NAN_DATALENGTH 0 #define NUMERIC_ZERO_DATALENGTH 2 /* * fmgr interface macros * DatumGetNumeric function rebuild */ #define DatumGetNumeric(X) ((Numeric)PG_DETOAST_DATUM(X)) #define DatumGetNumericCopy(X) ((Numeric)PG_DETOAST_DATUM_COPY(X)) #define NumericGetDatum(X) PointerGetDatum(X) #define PG_GETARG_NUMERIC(n) DatumGetNumeric(PG_GETARG_DATUM(n)) #define PG_GETARG_NUMERIC_COPY(n) DatumGetNumericCopy(PG_GETARG_DATUM(n)) #define PG_RETURN_NUMERIC(x) return NumericGetDatum(x) /* * Utility functions in numeric.c */ extern bool numeric_is_nan(Numeric num); int32 numeric_maximum_size(int32 typmod); extern char* numeric_out_sci(Numeric num, int scale); extern Datum numtodsinterval(PG_FUNCTION_ARGS); extern Datum bin_to_num(PG_FUNCTION_ARGS); extern Datum bin_to_num_noparam(PG_FUNCTION_ARGS); extern int cmp_numerics(Numeric num1, Numeric num2); extern int128 numeric_int16_internal(Numeric num); extern char* output_numeric_out(Numeric num); // // Numeric Compression Codes Area // #define INT32_MIN_ASCALE (-2) #define INT32_MAX_ASCALE (2) #define INT64_MIN_ASCALE (-4) #define INT64_MAX_ASCALE (4) extern const int16 INT16_MIN_VALUE; extern const int16 INT16_MAX_VALUE; extern const int32 INT32_MIN_VALUE; extern const int32 INT32_MAX_VALUE; extern const int64 INT64_MIN_VALUE; extern const int64 INT64_MAX_VALUE; #ifdef ENABLE_UT extern void test_dump_numeric_to_file(_out_ FILE* fd, _in_ const char* str, _in_ Numeric num); extern bool test_numeric_dscale_equal(_in_ Numeric v1, _in_ Numeric v2); extern void test_dump_compressed_numeric_to_file(_out_ FILE* fd, _in_ const char* str, _in_ int64 v, _in_ char ascale); extern char number_of_tail_zeros[10000]; #endif /* * convert functions between int32|int64|int128 and numerc * convert numeric to int32, int64 or int128 * convert int32, int64, int128 to numeric */ extern bool convert_short_numeric_to_int32(_in_ int64 v, _in_ char ascale); extern bool convert_short_numeric_to_int64(_in_ char* inBuf, _out_ int64* v, _out_ char* ascale); extern int convert_int64_to_short_numeric(_out_ char* outBuf, _in_ int64 v, _in_ char ascale, _in_ int32 typmod); extern int batch_convert_short_numeric_to_int64(_in_ Datum* batchValues, _in_ char* batchNulls, _in_ int batchRows, _in_ bool hasNull, _out_ int64* outInt, _out_ char* outAscales, _out_ bool* outSuccess, _out_ int* outNullCount); extern int convert_int64_to_short_numeric_byscale( _out_ char* outBuf, _in_ __int128_t v, _in_ int32 typmod, _in_ int32 vscale); extern int convert_int128_to_short_numeric_byscale( _out_ char* outBuf, _in_ int128 v, _in_ int32 typmod, _in_ int32 vscale); extern Datum convert_short_numeric_to_int64(_in_ Numeric inNum, _out_ bool* outSuccess); extern Datum convert_short_numeric_to_int128(_in_ Numeric inNum, _out_ bool* outSuccess); extern Datum try_convert_numeric_normal_to_fast(Datum value, ScalarVector *arr = NULL); extern Datum try_direct_convert_numeric_normal_to_fast(Datum value, ScalarVector *arr = NULL); extern int64 convert_short_numeric_to_int64_byscale(_in_ Numeric n, _in_ int scale); extern void convert_short_numeric_to_int128_byscale(_in_ Numeric n, _in_ int scale, _out_ int128& result); extern int32 get_ndigit_from_numeric(_in_ Numeric num); /* ---------- * 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. * 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. * * 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. * ---------- */ #define NUMERIC_LOCAL_NDIG 36 /* number of 'digits' in local digits[] */ #define NUMERIC_LOCAL_NMAX (NUMERIC_LOCAL_NDIG - 2) typedef struct NumericVar { int ndigits; /* # of digits in digits[] - can be 0! */ int weight; /* weight of first digit */ 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 */ NumericDigit ndb[NUMERIC_LOCAL_NDIG]; /* local space for digits[] */ } NumericVar; #define quick_init_var(v) \ do { \ (v)->buf = (v)->ndb; \ (v)->digits = NULL; \ } while (0) #define init_var(v) \ do { \ quick_init_var((v)); \ (v)->ndigits = 0; \ (v)->weight = 0; \ (v)->sign = 0; \ (v)->dscale = 0; \ } while (0) #define digitbuf_alloc(ndigits) \ ((NumericDigit*) palloc((ndigits) * sizeof(NumericDigit))) #define digitbuf_free(v) \ do { \ if ((v)->buf != (v)->ndb) { \ pfree((v)->buf); \ (v)->buf = (v)->ndb; \ } \ } while (0) #define free_var(v) digitbuf_free((v)); /* * Init a var and allocate digit buffer of ndigits digits (plus a spare digit for rounding). * Called when first using a var. */ #define init_alloc_var(v, n) \ do { \ (v)->buf = (v)->ndb; \ (v)->ndigits = (n); \ if ((n) > NUMERIC_LOCAL_NMAX) { \ (v)->buf = digitbuf_alloc((n) + 1); \ } \ (v)->buf[0] = 0; \ (v)->digits = (v)->buf + 1; \ } while (0) Numeric makeNumeric(NumericVar* var); extern Numeric make_result(NumericVar *var); extern void init_var_from_num(Numeric num, NumericVar* dest); extern bool numericvar_to_int64(const NumericVar* var, int64* result, bool can_ignore = false); extern void int64_to_numericvar(int64 val, NumericVar *var); extern void add_var(NumericVar *var1, NumericVar *var2, NumericVar *result); extern char *numeric_normalize(Numeric num); extern double numeric_to_double_no_overflow(Numeric num); bool numeric_agg_trans_initvalisnull(Oid transfn_oid, bool initvalisnull); void numeric_transfn_info_change(Oid aggfn_oid, Oid *transfn_oid, Oid *transtype); void numeric_finalfn_info_change(Oid aggdn_oid, Oid *finalfn_oid); void numeric_aggfn_info_change(Oid aggfn_oid, Oid *transfn_oid, Oid *transtype, Oid *finalfn_oid); #endif /* _PG_NUMERIC_H_ */