📄 numeric.c
字号:
/*------------------------------------------------------------------------- * * 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-2003, PostgreSQL Global Development Group * * IDENTIFICATION * $Header: /cvsroot/pgsql/src/backend/utils/adt/numeric.c,v 1.67 2003/09/29 00:05:25 petere Exp $ * *------------------------------------------------------------------------- */#include "postgres.h"#include <ctype.h>#include <float.h>#include <limits.h>#include <math.h>#include <errno.h>#include "catalog/pg_type.h"#include "libpq/pqformat.h"#include "utils/array.h"#include "utils/builtins.h"#include "utils/int8.h"#include "utils/numeric.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 definitions * ---------- */#ifndef NAN#define NAN (0.0/0.0)#endif/* ---------- * 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 to * postpone processing carries. * ---------- */#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 8typedef 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 6typedef 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 4typedef int16 NumericDigit;#endif/* ---------- * 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 special pushups. 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. * * 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. * ---------- */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 */} NumericVar;/* ---------- * Some preinitialized constants * ---------- */static NumericDigit const_zero_data[1] = {0};static NumericVar const_zero ={0, 0, NUMERIC_POS, 0, NULL, const_zero_data};static NumericDigit const_one_data[1] = {1};static NumericVar const_one ={1, 0, NUMERIC_POS, 0, NULL, const_one_data};static NumericDigit const_two_data[1] = {2};static NumericVar const_two ={1, 0, NUMERIC_POS, 0, NULL, const_two_data};#if DEC_DIGITS == 4static NumericDigit const_zero_point_five_data[1] = {5000};#elif DEC_DIGITS == 2static NumericDigit const_zero_point_five_data[1] = {50};#elif DEC_DIGITS == 1static NumericDigit const_zero_point_five_data[1] = {5};#endifstatic NumericVar const_zero_point_five ={1, -1, NUMERIC_POS, 1, NULL, const_zero_point_five_data};#if DEC_DIGITS == 4static NumericDigit const_zero_point_nine_data[1] = {9000};#elif DEC_DIGITS == 2static NumericDigit const_zero_point_nine_data[1] = {90};#elif DEC_DIGITS == 1static NumericDigit const_zero_point_nine_data[1] = {9};#endifstatic NumericVar const_zero_point_nine ={1, -1, NUMERIC_POS, 1, NULL, const_zero_point_nine_data};#if DEC_DIGITS == 4static NumericDigit const_zero_point_01_data[1] = {100};static NumericVar const_zero_point_01 ={1, -1, NUMERIC_POS, 2, NULL, const_zero_point_01_data};#elif DEC_DIGITS == 2static NumericDigit const_zero_point_01_data[1] = {1};static NumericVar const_zero_point_01 ={1, -1, NUMERIC_POS, 2, NULL, const_zero_point_01_data};#elif DEC_DIGITS == 1static NumericDigit const_zero_point_01_data[1] = {1};static NumericVar const_zero_point_01 ={1, -2, NUMERIC_POS, 2, NULL, const_zero_point_01_data};#endif#if DEC_DIGITS == 4static NumericDigit const_one_point_one_data[2] = {1, 1000};#elif DEC_DIGITS == 2static NumericDigit const_one_point_one_data[2] = {1, 10};#elif DEC_DIGITS == 1static NumericDigit const_one_point_one_data[2] = {1, 1};#endifstatic NumericVar const_one_point_one ={2, 0, NUMERIC_POS, 1, NULL, const_one_point_one_data};static NumericVar const_nan ={0, 0, NUMERIC_NAN, 0, NULL, NULL};#if DEC_DIGITS == 4static const int round_powers[4] = {0, 1000, 100, 10};#endif/* ---------- * Local functions * ---------- */#ifdef NUMERIC_DEBUGstatic 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))static void alloc_var(NumericVar *var, int ndigits);static void free_var(NumericVar *var);static void zero_var(NumericVar *var);static void set_var_from_str(const char *str, NumericVar *dest);static void set_var_from_num(Numeric value, NumericVar *dest);static void set_var_from_var(NumericVar *value, NumericVar *dest);static char *get_str_from_var(NumericVar *var, int dscale);static Numeric make_result(NumericVar *var);static void apply_typmod(NumericVar *var, int32 typmod);static bool numericvar_to_int8(NumericVar *var, int64 *result);static void int8_to_numericvar(int64 val, NumericVar *var);static double numeric_to_double_no_overflow(Numeric num);static double numericvar_to_double_no_overflow(NumericVar *var);static int cmp_numerics(Numeric num1, Numeric num2);static int cmp_var(NumericVar *var1, NumericVar *var2);static void add_var(NumericVar *var1, NumericVar *var2, NumericVar *result);static void sub_var(NumericVar *var1, NumericVar *var2, NumericVar *result);static void mul_var(NumericVar *var1, NumericVar *var2, NumericVar *result, int rscale);static void div_var(NumericVar *var1, NumericVar *var2, NumericVar *result, int rscale);static int select_div_scale(NumericVar *var1, NumericVar *var2);static void mod_var(NumericVar *var1, NumericVar *var2, NumericVar *result);static void ceil_var(NumericVar *var, NumericVar *result);static void floor_var(NumericVar *var, NumericVar *result);static void sqrt_var(NumericVar *arg, NumericVar *result, int rscale);static void exp_var(NumericVar *arg, NumericVar *result, int rscale);static void exp_var_internal(NumericVar *arg, NumericVar *result, int rscale);static void ln_var(NumericVar *arg, NumericVar *result, int rscale);static void log_var(NumericVar *base, NumericVar *num, NumericVar *result);static void power_var(NumericVar *base, NumericVar *exp, NumericVar *result);static void power_var_int(NumericVar *base, int exp, NumericVar *result, int rscale);static int cmp_abs(NumericVar *var1, NumericVar *var2);static void add_abs(NumericVar *var1, NumericVar *var2, NumericVar *result);static void sub_abs(NumericVar *var1, 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);/* ---------------------------------------------------------------------- * * Input-, output- and rounding-functions * * ---------------------------------------------------------------------- *//* * numeric_in() - * * Input function for numeric data type */Datumnumeric_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); NumericVar value; Numeric res; /* * Check for NaN */ if (strcasecmp(str, "NaN") == 0) PG_RETURN_NUMERIC(make_result(&const_nan)); /* * Use set_var_from_str() to parse the input string and return it in * the packed DB storage format */ init_var(&value); set_var_from_str(str, &value); apply_typmod(&value, typmod); res = make_result(&value); free_var(&value); PG_RETURN_NUMERIC(res);}/* * numeric_out() - * * Output function for numeric data type */Datumnumeric_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. * * Even if we didn't need to change format, we'd still need to copy the * value to have a modifiable copy for rounding. set_var_from_num() * also guarantees there is extra digit space in case we produce a * carry out from rounding. */ init_var(&x); set_var_from_num(num, &x); str = get_str_from_var(&x, x.dscale); free_var(&x); PG_RETURN_CSTRING(str);}/* * numeric_recv - converts external binary format to numeric * * External format is a sequence of int16's: * ndigits, weight, sign, dscale, NumericDigits. */Datumnumeric_recv(PG_FUNCTION_ARGS){ StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); NumericVar value; Numeric res; int len, i; init_var(&value); len = (uint16) pq_getmsgint(buf, sizeof(uint16)); if (len < 0 || len > NUMERIC_MAX_PRECISION + NUMERIC_MAX_RESULT_SCALE) ereport(ERROR, (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), errmsg("invalid length in external \"numeric\" value"))); alloc_var(&value, len); value.weight = (int16) pq_getmsgint(buf, sizeof(int16)); 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)); for (i = 0; i < len; i++) { NumericDigit d = pq_getmsgint(buf, sizeof(NumericDigit)); if (d < 0 || d >= NBASE) ereport(ERROR, (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), errmsg("invalid digit in external \"numeric\" value"))); value.digits[i] = d; } res = make_result(&value); free_var(&value); PG_RETURN_NUMERIC(res);}/* * numeric_send - converts numeric to binary format */Datumnumeric_send(PG_FUNCTION_ARGS){ Numeric num = PG_GETARG_NUMERIC(0); NumericVar x; StringInfoData buf; int i; init_var(&x); set_var_from_num(num, &x); pq_begintypsend(&buf); pq_sendint(&buf, x.ndigits, sizeof(int16)); pq_sendint(&buf, x.weight, sizeof(int16)); pq_sendint(&buf, x.sign, sizeof(int16)); pq_sendint(&buf, x.dscale, sizeof(int16)); for (i = 0; i < x.ndigits; i++) pq_sendint(&buf, x.digits[i], sizeof(NumericDigit)); free_var(&x); PG_RETURN_BYTEA_P(pq_endtypsend(&buf));}/* * 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 * scale of the attribute have to be applied on the value. */Datumnumeric(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)); /* * If the value isn't a valid type modifier, simply return a copy of * the input value */ if (typmod < (int32) (VARHDRSZ)) { new = (Numeric) palloc(num->varlen); memcpy(new, num, num->varlen); PG_RETURN_NUMERIC(new); } /* * Get the precision and scale out of the typmod value */ tmp_typmod = typmod - VARHDRSZ; precision = (tmp_typmod >> 16) & 0xffff; scale = tmp_typmod & 0xffff; maxdigits = precision - scale; /* * If the number is certainly in bounds and due to the target scale no * rounding could be necessary, just make a copy of the input and * modify its scale fields. (Note we assume the existing dscale is * honest...) */ ddigits = (num->n_weight + 1) * DEC_DIGITS; if (ddigits <= maxdigits && scale >= NUMERIC_DSCALE(num)) { new = (Numeric) palloc(num->varlen); memcpy(new, num, num->varlen); new->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);}/* ---------------------------------------------------------------------- * * Sign manipulation, rounding and the like * * ---------------------------------------------------------------------- */Datumnumeric_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(num->varlen); memcpy(res, num, num->varlen); res->n_sign_dscale = NUMERIC_POS | NUMERIC_DSCALE(num); PG_RETURN_NUMERIC(res);}Datumnumeric_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(num->varlen); memcpy(res, num, num->varlen); /* * 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 nothing to a zero. */ if (num->varlen != NUMERIC_HDRSZ) { /* Else, flip the sign */ if (NUMERIC_SIGN(num) == NUMERIC_POS) res->n_sign_dscale = NUMERIC_NEG | NUMERIC_DSCALE(num);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -