/* * numeric.c * Type handler for the numeric data types. * * Copyright (c) 2011 eSilo, LLC. All rights reserved. * This is free software; see the source for copying conditions. There is * NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. */ #include "libpqtypes-int.h" #include /* Extremely annoying warning "conversion from 'int ' to 'short'" on * Visual Studio 6. Normally this warning is useful but not in this * case. Disable it. */ #if defined(PQT_MSVC) && PQT_MSVC <= 1200 # pragma warning (disable : 4244) #endif /* * Macros and structures for receiving numeric field in binary */ #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 /* * Hardcoded precision limit - arbitrary, but must be small enough that * dscale values will fit in 14 bits. */ #define NUMERIC_MAX_PRECISION 1000 /* * Sign values and macros to deal with packing/unpacking n_sign_dscale */ #define NUMERIC_SIGN_MASK 0xC000 #define NUMERIC_POS 0x0000 #define NUMERIC_NEG 0x4000 #define NUMERIC_NAN 0xC000 #define NUMERIC_DSCALE_MASK 0x3FFF #define NUMERIC_SIGN(n) ((n)->n_sign_dscale & NUMERIC_SIGN_MASK) #define NUMERIC_DSCALE(n) ((n)->n_sign_dscale & NUMERIC_DSCALE_MASK) #define NUMERIC_IS_NAN(n) (NUMERIC_SIGN(n) != NUMERIC_POS && \ NUMERIC_SIGN(n) != NUMERIC_NEG) typedef short NumericDigit; static const int round_powers[4] = {0, 1000, 100, 10}; 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; static int str2num(PGtypeArgs *args, const char *str, NumericVar *dest); static int num2str(char *out, size_t outl, NumericVar *var, int dscale); int pqt_put_int2(PGtypeArgs *args) { PGint2 *i2p = va_arg(args->ap, PGint2 *); PUTNULLCHK(args, i2p); pqt_buf_putint2(args->put.out, *i2p); return 2; } int pqt_get_int2(PGtypeArgs *args) { DECLVALUE(args); PGint2 *i2p = va_arg(args->ap, PGint2 *); CHKGETVALS(args, i2p); if (args->format == TEXTFMT) { int n; errno = 0; if ((n = (int) strtol(value, NULL, 10)) == 0 && errno) RERR_STR2INT(args); *i2p = (PGint2)n; return 0; } *i2p = pqt_buf_getint2(value); return 0; } /* handles oid as well */ int pqt_put_int4(PGtypeArgs *args) { PGint4 *i4p = va_arg(args->ap, PGint4 *); PUTNULLCHK(args, i4p); *(PGint4 *)args->put.out = (PGint4) htonl(*i4p); return 4; } /* handles oid as well */ int pqt_get_int4(PGtypeArgs *args) { DECLVALUE(args); PGint4 *i4p = va_arg(args->ap, PGint4 *); CHKGETVALS(args, i4p); if (args->format == TEXTFMT) { PGint4 n; /* Use strtoul so this can support Oid */ if ((n = (PGint4) strtoul(value, NULL, 10)) == 0 && errno) RERR_STR2INT(args); *i4p = n; return 0; } *i4p = (PGint4) pqt_buf_getint4(value); return 0; } int pqt_put_int8(PGtypeArgs *args) { PGint8 *i8p = va_arg(args->ap, PGint8 *); PUTNULLCHK(args, i8p); pqt_swap8(args->put.out, i8p, 1); return 8; } int pqt_get_int8(PGtypeArgs *args) { DECLVALUE(args); PGint8 *i8p = va_arg(args->ap, PGint8 *); CHKGETVALS(args, i8p); if (args->format == TEXTFMT) { if (pqt_text_to_int8(value, i8p) == -1) RERR_STR2INT(args); return 0; } pqt_swap8(i8p, value, 0); return 0; } int pqt_put_float4(PGtypeArgs *args) { PGfloat4 *f4p = va_arg(args->ap, PGfloat4 *); PUTNULLCHK(args, f4p); void *vp = (void *)f4p; pqt_buf_putint4(args->put.out, *(int *) vp); return 4; } int pqt_get_float4(PGtypeArgs *args) { DECLVALUE(args); int *f4p = (int *) va_arg(args->ap, PGfloat4 *); CHKGETVALS(args, f4p); if (args->format == TEXTFMT) { PGfloat4 f; errno = 0; if ((f = (PGfloat4) strtod(value, NULL)) == 0 && errno) RERR_STR2INT(args); *(PGfloat4 *) f4p = f; return 0; } *f4p = pqt_buf_getint4(value); return 0; } int pqt_put_float8(PGtypeArgs *args) { PGfloat8 *f8p = va_arg(args->ap, PGfloat8 *); PUTNULLCHK(args, f8p); pqt_swap8(args->put.out, f8p, 1); return 8; } int pqt_get_float8(PGtypeArgs *args) { DECLVALUE(args); PGfloat8 *f8p = va_arg(args->ap, PGfloat8 *); CHKGETVALS(args, f8p); if (args->format == TEXTFMT) { if (!pqt_text_to_float8(f8p, value, NULL)) RERR_STR2INT(args); return 0; } pqt_swap8(f8p, value, 0); return 0; } /* exposing a NumericVar struct to a libpq user, or something similar, * doesn't seem useful w/o a library to operate on it. Instead, we * always expose a numeric in text format and let the API user decide * how to use it .. like strod or a 3rd party big number library. We * always send a numeric in binary though. */ int pqt_put_numeric(PGtypeArgs *args) { int numlen; NumericVar num = {0}; short *out; PGnumeric str = va_arg(args->ap, PGnumeric); PUTNULLCHK(args, str); if (str2num(args, str, &num)) { if (num.digits) free(num.digits); return -1; } /* variable length data type, grow args->put.out buffer if needed */ numlen = (int) sizeof(short) * (4 + num.ndigits); if (args->put.expandBuffer(args, numlen) == -1) return -1; out = (short *) args->put.out; *out++ = htons((short) num.ndigits); *out++ = htons((short) num.weight); *out++ = htons((short) num.sign); *out++ = htons((short) num.dscale); if (num.digits) { int i; for (i=0; i < num.ndigits; i++) *out++ = htons(num.digits[i]); free(num.digits); } return numlen; } /* exposing a NumericVar struct to a libpq user, or something similar, * doesn't seem useful w/o a library to operate on it. Instead, we * always expose a numeric in text format and let the API user decide * how to use it .. like strod or a 3rd party big number library. */ int pqt_get_numeric(PGtypeArgs *args) { int i; short *s; NumericVar num; DECLVALUE(args); char buf[4096]; size_t len; PGnumeric *str = va_arg(args->ap, PGnumeric *); CHKGETVALS(args, str); if (args->format == TEXTFMT) { *str = value; return 0; } s = (short *) value; num.ndigits = ntohs(*s); s++; num.weight = ntohs(*s); s++; num.sign = ntohs(*s); s++; num.dscale = ntohs(*s); s++; num.digits = (short *) malloc(num.ndigits * sizeof(short)); if (!num.digits) RERR_MEM(args); for (i=0; i < num.ndigits; i++) { num.digits[i] = ntohs(*s); s++; } i = num2str(buf, sizeof(buf), &num, num.dscale); free(num.digits); /* num2str failed, only fails when 'str' is too small */ if (i == -1) RERR(args, "out buffer is too small"); len = strlen(buf)+1; *str = PQresultAlloc(args->get.result, len); if (!*str) RERR_MEM(args); memcpy(*str, buf, len); return 0; } /* * 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) { NumericDigit *digits = var->digits; int di; int ndigits; int carry; var->dscale = rscale; /* decimal digits wanted */ di = (var->weight + 1) * DEC_DIGITS + rscale; /* * 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 */ 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 (di == 0) carry = (digits[ndigits] >= HALF_NBASE) ? 1 : 0; else { /* Must round within last NBASE digit */ int extra, pow10; pow10 = round_powers[di]; extra = digits[--ndigits] % pow10; digits[ndigits] = digits[ndigits] - (NumericDigit) extra; carry = 0; if (extra >= pow10 / 2) { pow10 += digits[ndigits]; if (pow10 >= NBASE) { pow10 -= NBASE; carry = 1; } digits[ndigits] = (NumericDigit) pow10; } } /* Propagate carry if needed */ while (carry) { carry += digits[--ndigits]; if (carry >= NBASE) { digits[ndigits] = (NumericDigit) (carry - NBASE); carry = 1; } else { digits[ndigits] = (NumericDigit) carry; carry = 0; } } if (ndigits < 0) { var->digits--; var->ndigits++; var->weight++; } } } } /* * strip_var * * Strip any leading and trailing zeroes from a numeric variable */ static void strip_var(NumericVar *var) { 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; } /* * str2num() * * Parse a string and put the number into a variable * returns -1 on error and 0 for success. */ static int str2num(PGtypeArgs *args, const char *str, NumericVar *dest) { const char *cp = str; int 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)) return args->errorf(args, "invalid input syntax for type numeric: '%s'", str); decdigits = (unsigned char *) malloc(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) { free(decdigits); return args->errorf(args, "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) { free(decdigits); return args->errorf(args, "invalid input syntax for type numeric: '%s'", str); } cp = endptr; if (exponent > NUMERIC_MAX_PRECISION || exponent < -NUMERIC_MAX_PRECISION) { free(decdigits); return args->errorf(args, "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)) { free(decdigits); return args->errorf(args, "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; dest->digits = (NumericDigit *) malloc((ndigits) * sizeof(NumericDigit)); dest->ndigits = ndigits; dest->sign = sign; dest->weight = weight; dest->dscale = dscale; i = DEC_DIGITS - offset; digits = dest->digits; while (ndigits-- > 0) { *digits++ = ((decdigits[i] * 10 + decdigits[i + 1]) * 10 + decdigits[i + 2]) * 10 + decdigits[i + 3]; i += DEC_DIGITS; } free(decdigits); /* Strip any leading/trailing zeroes, and normalize weight if zero */ strip_var(dest); return 0; } /* * num2str() - * * Convert a var to text representation (guts of numeric_out). * CAUTION: var's contents may be modified by rounding! * returns -1 on error and 0 for success. */ static int num2str(char *out, size_t outl, NumericVar *var, int dscale) { //char *str; char *cp; char *endcp; int i; int d; NumericDigit dig; NumericDigit d1; 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; if (outl <= (size_t) (i + dscale + DEC_DIGITS + 2)) return -1; //str = palloc(i + dscale + DEC_DIGITS + 2); //cp = str cp = out; /* * 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 */ { int putit = (d > 0); d1 = dig / 1000; dig -= d1 * 1000; putit |= (d1 > 0); if (putit) *cp++ = (char) (d1 + '0'); d1 = dig / 100; dig -= d1 * 100; putit |= (d1 > 0); if (putit) *cp++ = (char) (d1 + '0'); d1 = dig / 10; dig -= d1 * 10; putit |= (d1 > 0); if (putit) *cp++ = (char) (d1 + '0'); *cp++ = (char) (dig + '0'); } } } /* * 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; d1 = dig / 1000; dig -= d1 * 1000; *cp++ = (char) (d1 + '0'); d1 = dig / 100; dig -= d1 * 100; *cp++ = (char) (d1 + '0'); d1 = dig / 10; dig -= d1 * 10; *cp++ = (char) (d1 + '0'); *cp++ = (char) (dig + '0'); } cp = endcp; } /* * terminate the string and return it */ *cp = '\0'; return 0; }