/* -------------------------------------------------------------------------
 *
 * 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_ */