📄 numeric.c
字号:
static voiddump_var(const char *str, NumericVar *var){ int i; printf("%s: VAR w=%d d=%d ", str, var->weight, var->dscale); switch (var->sign) { 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]); printf("\n");}#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 voidalloc_var(NumericVar *var, int ndigits){ digitbuf_free(var->buf); var->buf = digitbuf_alloc(ndigits + 1); 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 voidfree_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 voidzero_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 */static voidset_var_from_str(const char *str, NumericVar *dest){ const char *cp = str; 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; /* * We first parse the string to extract decimal digits and determine * the correct decimal weight. Then convert to NBASE representation. */ /* skip leading spaces */ while (*cp) { if (!isspace((unsigned char) *cp)) break; cp++; } switch (*cp) { case '+': sign = NUMERIC_POS; cp++; break; case '-': sign = NUMERIC_NEG; 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 numeric: \"%s\"", str))); 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 numeric: \"%s\"", str))); have_dp = TRUE; cp++; } else break; } ddigits = i - DEC_DIGITS; /* trailing padding for digit alignment later */ 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 numeric: \"%s\"", str))); cp = endptr; if (exponent > NUMERIC_MAX_PRECISION || exponent < -NUMERIC_MAX_PRECISION) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type numeric: \"%s\"", str))); dweight += (int) exponent; dscale -= (int) exponent; if (dscale < 0) dscale = 0; } /* Should be nothing left but spaces */ while (*cp) { if (!isspace((unsigned char) *cp)) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type numeric: \"%s\"", str))); cp++; } /* * 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) weight = (dweight + 1 + DEC_DIGITS - 1) / DEC_DIGITS - 1; else weight = -((-dweight - 1) / DEC_DIGITS + 1); offset = (weight + 1) * DEC_DIGITS - (dweight + 1); 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 *digits++ = ((decdigits[i] * 10 + decdigits[i + 1]) * 10 + decdigits[i + 2]) * 10 + decdigits[i + 3];#elif DEC_DIGITS == 2 *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);}/* * set_var_from_num() - * * Convert the packed db format into a variable */static voidset_var_from_num(Numeric num, NumericVar *dest){ int ndigits; ndigits = (num->varlen - NUMERIC_HDRSZ) / sizeof(NumericDigit); alloc_var(dest, ndigits); dest->weight = num->n_weight; dest->sign = NUMERIC_SIGN(num); dest->dscale = NUMERIC_DSCALE(num); memcpy(dest->digits, num->n_data, ndigits * sizeof(NumericDigit));}/* * set_var_from_var() - * * Copy one variable into another */static voidset_var_from_var(NumericVar *value, NumericVar *dest){ NumericDigit *newbuf; newbuf = digitbuf_alloc(value->ndigits + 1); newbuf[0] = 0; /* spare digit for rounding */ memcpy(newbuf + 1, value->digits, value->ndigits * sizeof(NumericDigit)); digitbuf_free(dest->buf); memcpy(dest, value, sizeof(NumericVar)); dest->buf = newbuf; dest->digits = newbuf + 1;}/* * get_str_from_var() - * * Convert a var to text representation (guts of numeric_out). * CAUTION: var's contents may be modified by rounding! * Returns a palloc'd string. */static char *get_str_from_var(NumericVar *var, int dscale){ char *str; char *cp; char *endcp; int i; int d; NumericDigit dig;#if DEC_DIGITS > 1 NumericDigit d1;#endif if (dscale < 0) dscale = 0; /* * Check if we must round up before printing the value and do so. */ round_var(var, dscale); /* * Allocate space for the result. * * i is set to to # 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 { 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 } } /* * 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;}/* * make_result() - * * Create the packed db numeric format in palloc()'d memory from * a variable. */static Numericmake_result(NumericVar *var){ Numeric result; NumericDigit *digits = var->digits; int weight = var->weight; int sign = var->sign; int n; Size len; if (sign == NUMERIC_NAN) { result = (Numeric) palloc(NUMERIC_HDRSZ); result->varlen = NUMERIC_HDRSZ; result->n_weight = 0; result->n_sign_dscale = NUMERIC_NAN; 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; sign = NUMERIC_POS; } /* Build the result */ len = NUMERIC_HDRSZ + n * sizeof(NumericDigit); result = (Numeric) palloc(len); result->varlen = len; result->n_weight = weight; result->n_sign_dscale = sign | (var->dscale & NUMERIC_DSCALE_MASK); memcpy(result->n_data, digits, n * sizeof(NumericDigit)); /* Check for overflow of int16 fields */ if (result->n_weight != weight || NUMERIC_DSCALE(result) != var->dscale) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("value overflows numeric format"))); dump_numeric("make_result()", result); return result;}/* * apply_typmod() - * * Do bounds checking and rounding according to the attributes * typmod field. */static voidapply_typmod(NumericVar *var, int32 typmod){ int precision; int scale; int maxdigits; int ddigits; int i; /* Do nothing if we have a default typmod (-1) */ if (typmod < (int32) (VARHDRSZ)) return; typmod -= VARHDRSZ; precision = (typmod >> 16) & 0xffff; 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 * 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("The absolute value is greater than or equal to 10^%d for field with precision %d, scale %d.", ddigits - 1, precision, scale))); break; } ddigits -= DEC_DIGITS; } }}/* * Convert numeric to int8, rounding if needed. * * If overflow, return FALSE (no error
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -