1 2 /* 3 * numeric.c 4 * Type handler for the numeric data types. 5 * 6 * Copyright (c) 2009 eSilo, LLC. All rights reserved. 7 * This is free software; see the source for copying conditions. There is 8 * NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR 9 * PURPOSE. 10 */ 11 12 #include "libpqtypes-int.h" 13 #include <errno.h> 14 15 /* Extremely annoying warning "conversion from 'int ' to 'short'" on 16 * Visual Studio 6. Normally this warning is useful but not in this 17 * case. Disable it. 18 */ 19 #if defined(PQT_MSVC) && PQT_MSVC <= 1200 20 # pragma warning (disable : 4244) 21 #endif 22 23 /* 24 * Macros and structures for receiving numeric field in binary 25 */ 26 #define NBASE 10000 27 #define HALF_NBASE 5000 28 #define DEC_DIGITS 4 /* decimal digits per NBASE digit */ 29 #define MUL_GUARD_DIGITS 2 /* these are measured in NBASE digits */ 30 #define DIV_GUARD_DIGITS 4 31 32 /* 33 * Hardcoded precision limit - arbitrary, but must be small enough that 34 * dscale values will fit in 14 bits. 35 */ 36 #define NUMERIC_MAX_PRECISION 1000 37 38 /* 39 * Sign values and macros to deal with packing/unpacking n_sign_dscale 40 */ 41 #define NUMERIC_SIGN_MASK 0xC000 42 #define NUMERIC_POS 0x0000 43 #define NUMERIC_NEG 0x4000 44 #define NUMERIC_NAN 0xC000 45 #define NUMERIC_DSCALE_MASK 0x3FFF 46 #define NUMERIC_SIGN(n) ((n)->n_sign_dscale & NUMERIC_SIGN_MASK) 47 #define NUMERIC_DSCALE(n) ((n)->n_sign_dscale & NUMERIC_DSCALE_MASK) 48 #define NUMERIC_IS_NAN(n) (NUMERIC_SIGN(n) != NUMERIC_POS && \ 49 NUMERIC_SIGN(n) != NUMERIC_NEG) 50 51 typedef short NumericDigit; 52 static const int round_powers[4] = {0, 1000, 100, 10}; 53 54 typedef struct NumericVar 55 { 56 int ndigits; /* # of digits in digits[] - can be 0! */ 57 int weight; /* weight of first digit */ 58 int sign; /* NUMERIC_POS, NUMERIC_NEG, or NUMERIC_NAN */ 59 int dscale; /* display scale */ 60 NumericDigit *buf; /* start of palloc'd space for digits[] */ 61 NumericDigit *digits; /* base-NBASE digits */ 62 } NumericVar; 63 64 static int str2num(PGtypeArgs *args, const char *str, NumericVar *dest); 65 static int num2str(char *out, size_t outl, NumericVar *var, int dscale); 66 67 int 68 pqt_put_int2(PGtypeArgs *args) 69 { 70 PGint2 n = (PGint2) va_arg(args->ap, int); 71 pqt_buf_putint2(args->put.out, n); 72 return 2; 73 } 74 75 int 76 pqt_get_int2(PGtypeArgs *args) 77 { 78 DECLVALUE(args); 79 PGint2 *i2p = va_arg(args->ap, PGint2 *); 80 81 CHKGETVALS(args, i2p); 82 83 if (args->format == TEXTFMT) 84 { 85 int n; 86 87 errno = 0; 88 if ((n = (int) strtol(value, NULL, 10)) == 0 && errno) 89 RERR_STR2INT(args); 90 91 *i2p = (PGint2)n; 92 return 0; 93 } 94 95 *i2p = pqt_buf_getint2(value); 96 return 0; 97 } 98 99 /* handles oid as well */ 100 int 101 pqt_put_int4(PGtypeArgs *args) 102 { 103 PGint4 n = va_arg(args->ap, PGint4); 104 *(PGint4 *)args->put.out = (PGint4) htonl(n); 105 return 4; 106 } 107 108 /* handles oid as well */ 109 int 110 pqt_get_int4(PGtypeArgs *args) 111 { 112 DECLVALUE(args); 113 PGint4 *i4p = va_arg(args->ap, PGint4 *); 114 115 CHKGETVALS(args, i4p); 116 117 if (args->format == TEXTFMT) 118 { 119 PGint4 n; 120 121 /* Use strtoul so this can support Oid */ 122 if ((n = (PGint4) strtoul(value, NULL, 10)) == 0 && errno) 123 RERR_STR2INT(args); 124 125 *i4p = n; 126 return 0; 127 } 128 129 *i4p = (PGint4) pqt_buf_getint4(value); 130 return 0; 131 } 132 133 int 134 pqt_put_int8(PGtypeArgs *args) 135 { 136 PGint8 i8 = va_arg(args->ap, PGint8); 137 pqt_swap8(args->put.out, &i8, 1); 138 return 8; 139 } 140 141 int 142 pqt_get_int8(PGtypeArgs *args) 143 { 144 DECLVALUE(args); 145 PGint8 *i8p = va_arg(args->ap, PGint8 *); 146 147 CHKGETVALS(args, i8p); 148 149 if (args->format == TEXTFMT) 150 { 151 if (pqt_text_to_int8(value, i8p) == -1) 152 RERR_STR2INT(args); 153 return 0; 154 } 155 156 pqt_swap8(i8p, value, 0); 157 return 0; 158 } 159 160 int 161 pqt_put_float4(PGtypeArgs *args) 162 { 163 PGfloat4 f = (PGfloat4) va_arg(args->ap, double); 164 void *vp = (void *)&f; 165 pqt_buf_putint4(args->put.out, *(int *) vp); 166 return 4; 167 } 168 169 int 170 pqt_get_float4(PGtypeArgs *args) 171 { 172 DECLVALUE(args); 173 int *f4p = (int *) va_arg(args->ap, PGfloat4 *); 174 175 CHKGETVALS(args, f4p); 176 177 if (args->format == TEXTFMT) 178 { 179 PGfloat4 f; 180 181 errno = 0; 182 if ((f = (PGfloat4) strtod(value, NULL)) == 0 && errno) 183 RERR_STR2INT(args); 184 185 *(PGfloat4 *) f4p = f; 186 return 0; 187 } 188 189 *f4p = pqt_buf_getint4(value); 190 return 0; 191 } 192 193 int 194 pqt_put_float8(PGtypeArgs *args) 195 { 196 PGfloat8 d = va_arg(args->ap, PGfloat8); 197 pqt_swap8(args->put.out, &d, 1); 198 return 8; 199 } 200 201 int 202 pqt_get_float8(PGtypeArgs *args) 203 { 204 DECLVALUE(args); 205 PGfloat8 *f8p = va_arg(args->ap, PGfloat8 *); 206 207 CHKGETVALS(args, f8p); 208 209 if (args->format == TEXTFMT) 210 { 211 if (!pqt_text_to_float8(f8p, value, NULL)) 212 RERR_STR2INT(args); 213 214 return 0; 215 } 216 217 pqt_swap8(f8p, value, 0); 218 return 0; 219 } 220 221 /* exposing a NumericVar struct to a libpq user, or something similar, 222 * doesn't seem useful w/o a library to operate on it. Instead, we 223 * always expose a numeric in text format and let the API user decide 224 * how to use it .. like strod or a 3rd party big number library. We 225 * always send a numeric in binary though. 226 */ 227 int 228 pqt_put_numeric(PGtypeArgs *args) 229 { 230 int numlen; 231 NumericVar num = {0}; 232 short *out; 233 PGnumeric str = va_arg(args->ap, PGnumeric); 234 235 if (str2num(args, str, &num)) 236 { 237 if (num.digits) 238 free(num.digits); 239 return -1; 240 } 241 242 /* variable length data type, grow args->put.out buffer if needed */ 243 numlen = (int) sizeof(short) * (4 + num.ndigits); 244 if (args->put.expandBuffer(args, numlen) == -1) 245 return -1; 246 247 out = (short *) args->put.out; 248 *out++ = htons((short) num.ndigits); 249 *out++ = htons((short) num.weight); 250 *out++ = htons((short) num.sign); 251 *out++ = htons((short) num.dscale); 252 253 if (num.digits) 254 { 255 int i; 256 for (i=0; i < num.ndigits; i++) 257 *out++ = htons(num.digits[i]); 258 free(num.digits); 259 } 260 261 return numlen; 262 } 263 264 /* exposing a NumericVar struct to a libpq user, or something similar, 265 * doesn't seem useful w/o a library to operate on it. Instead, we 266 * always expose a numeric in text format and let the API user decide 267 * how to use it .. like strod or a 3rd party big number library. 268 */ 269 int 270 pqt_get_numeric(PGtypeArgs *args) 271 { 272 int i; 273 short *s; 274 NumericVar num; 275 DECLVALUE(args); 276 char buf[4096]; 277 size_t len; 278 PGnumeric *str = va_arg(args->ap, PGnumeric *); 279 280 CHKGETVALS(args, str); 281 282 if (args->format == TEXTFMT) 283 { 284 *str = value; 285 return 0; 286 } 287 288 s = (short *) value; 289 num.ndigits = ntohs(*s); s++; 290 num.weight = ntohs(*s); s++; 291 num.sign = ntohs(*s); s++; 292 num.dscale = ntohs(*s); s++; 293 num.digits = (short *) malloc(num.ndigits * sizeof(short)); 294 if (!num.digits) 295 RERR_MEM(args); 296 297 for (i=0; i < num.ndigits; i++) 298 { 299 num.digits[i] = ntohs(*s); 300 s++; 301 } 302 303 i = num2str(buf, sizeof(buf), &num, num.dscale); 304 free(num.digits); 305 306 /* num2str failed, only fails when 'str' is too small */ 307 if (i == -1) 308 RERR(args, "out buffer is too small"); 309 310 len = strlen(buf)+1; 311 *str = PQresultAlloc(args->get.result, len); 312 if (!*str) 313 RERR_MEM(args); 314 315 memcpy(*str, buf, len); 316 return 0; 317 318 } 319 320 321 /* 322 * round_var 323 * 324 * Round the value of a variable to no more than rscale decimal digits 325 * after the decimal point. NOTE: we allow rscale < 0 here, implying 326 * rounding before the decimal point. 327 */ 328 static void 329 round_var(NumericVar *var, int rscale) 330 { 331 NumericDigit *digits = var->digits; 332 int di; 333 int ndigits; 334 int carry; 335 336 var->dscale = rscale; 337 338 /* decimal digits wanted */ 339 di = (var->weight + 1) * DEC_DIGITS + rscale; 340 341 /* 342 * If di = 0, the value loses all digits, but could round up to 1 if its 343 * first extra digit is >= 5. If di < 0 the result must be 0. 344 */ 345 if (di < 0) 346 { 347 var->ndigits = 0; 348 var->weight = 0; 349 var->sign = NUMERIC_POS; 350 } 351 else 352 { 353 /* NBASE digits wanted */ 354 ndigits = (di + DEC_DIGITS - 1) / DEC_DIGITS; 355 356 /* 0, or number of decimal digits to keep in last NBASE digit */ 357 di %= DEC_DIGITS; 358 359 if (ndigits < var->ndigits || 360 (ndigits == var->ndigits && di > 0)) 361 { 362 var->ndigits = ndigits; 363 364 if (di == 0) 365 carry = (digits[ndigits] >= HALF_NBASE) ? 1 : 0; 366 else 367 { 368 /* Must round within last NBASE digit */ 369 int extra, 370 pow10; 371 372 pow10 = round_powers[di]; 373 extra = digits[--ndigits] % pow10; 374 digits[ndigits] = digits[ndigits] - (NumericDigit) extra; 375 carry = 0; 376 if (extra >= pow10 / 2) 377 { 378 pow10 += digits[ndigits]; 379 if (pow10 >= NBASE) 380 { 381 pow10 -= NBASE; 382 carry = 1; 383 } 384 digits[ndigits] = (NumericDigit) pow10; 385 } 386 } 387 388 /* Propagate carry if needed */ 389 while (carry) 390 { 391 carry += digits[--ndigits]; 392 if (carry >= NBASE) 393 { 394 digits[ndigits] = (NumericDigit) (carry - NBASE); 395 carry = 1; 396 } 397 else 398 { 399 digits[ndigits] = (NumericDigit) carry; 400 carry = 0; 401 } 402 } 403 404 if (ndigits < 0) 405 { 406 var->digits--; 407 var->ndigits++; 408 var->weight++; 409 } 410 } 411 } 412 } 413 414 /* 415 * strip_var 416 * 417 * Strip any leading and trailing zeroes from a numeric variable 418 */ 419 static void 420 strip_var(NumericVar *var) 421 { 422 NumericDigit *digits = var->digits; 423 int ndigits = var->ndigits; 424 425 /* Strip leading zeroes */ 426 while (ndigits > 0 && *digits == 0) 427 { 428 digits++; 429 var->weight--; 430 ndigits--; 431 } 432 433 /* Strip trailing zeroes */ 434 while (ndigits > 0 && digits[ndigits - 1] == 0) 435 ndigits--; 436 437 /* If it's zero, normalize the sign and weight */ 438 if (ndigits == 0) 439 { 440 var->sign = NUMERIC_POS; 441 var->weight = 0; 442 } 443 444 var->digits = digits; 445 var->ndigits = ndigits; 446 } 447 448 /* 449 * str2num() 450 * 451 * Parse a string and put the number into a variable 452 * returns -1 on error and 0 for success. 453 */ 454 static int 455 str2num(PGtypeArgs *args, const char *str, NumericVar *dest) 456 { 457 const char *cp = str; 458 int have_dp = FALSE; 459 int i; 460 unsigned char *decdigits; 461 int sign = NUMERIC_POS; 462 int dweight = -1; 463 int ddigits; 464 int dscale = 0; 465 int weight; 466 int ndigits; 467 int offset; 468 NumericDigit *digits; 469 470 /* 471 * We first parse the string to extract decimal digits and determine the 472 * correct decimal weight. Then convert to NBASE representation. 473 */ 474 475 /* skip leading spaces */ 476 while (*cp) 477 { 478 if (!isspace((unsigned char) *cp)) 479 break; 480 cp++; 481 } 482 483 switch (*cp) 484 { 485 case '+': 486 sign = NUMERIC_POS; 487 cp++; 488 break; 489 490 case '-': 491 sign = NUMERIC_NEG; 492 cp++; 493 break; 494 } 495 496 if (*cp == '.') 497 { 498 have_dp = TRUE; 499 cp++; 500 } 501 502 if (!isdigit((unsigned char) *cp)) 503 return args->errorf(args, 504 "invalid input syntax for type numeric: '%s'", str); 505 506 decdigits = (unsigned char *) malloc(strlen(cp) + DEC_DIGITS * 2); 507 508 /* leading padding for digit alignment later */ 509 memset(decdigits, 0, DEC_DIGITS); 510 i = DEC_DIGITS; 511 512 while (*cp) 513 { 514 if (isdigit((unsigned char) *cp)) 515 { 516 decdigits[i++] = *cp++ - '0'; 517 if (!have_dp) 518 dweight++; 519 else 520 dscale++; 521 } 522 else if (*cp == '.') 523 { 524 if (have_dp) 525 { 526 free(decdigits); 527 return args->errorf(args, 528 "invalid input syntax for type numeric: '%s'", str); 529 } 530 531 have_dp = TRUE; 532 cp++; 533 } 534 else 535 break; 536 } 537 538 ddigits = i - DEC_DIGITS; 539 /* trailing padding for digit alignment later */ 540 memset(decdigits + i, 0, DEC_DIGITS - 1); 541 542 /* Handle exponent, if any */ 543 if (*cp == 'e' || *cp == 'E') 544 { 545 long exponent; 546 char *endptr; 547 548 cp++; 549 exponent = strtol(cp, &endptr, 10); 550 if (endptr == cp) 551 { 552 free(decdigits); 553 return args->errorf(args, 554 "invalid input syntax for type numeric: '%s'", str); 555 } 556 557 cp = endptr; 558 if (exponent > NUMERIC_MAX_PRECISION || 559 exponent < -NUMERIC_MAX_PRECISION) 560 { 561 free(decdigits); 562 return args->errorf(args, 563 "invalid input syntax for type numeric: '%s'", str); 564 } 565 566 dweight += (int) exponent; 567 dscale -= (int) exponent; 568 if (dscale < 0) 569 dscale = 0; 570 } 571 572 /* Should be nothing left but spaces */ 573 while (*cp) 574 { 575 if (!isspace((unsigned char) *cp)) 576 { 577 free(decdigits); 578 return args->errorf(args, 579 "invalid input syntax for type numeric: '%s'", str); 580 } 581 cp++; 582 } 583 584 /* 585 * Okay, convert pure-decimal representation to base NBASE. First we need 586 * to determine the converted weight and ndigits. offset is the number of 587 * decimal zeroes to insert before the first given digit to have a 588 * correctly aligned first NBASE digit. 589 */ 590 if (dweight >= 0) 591 weight = (dweight + 1 + DEC_DIGITS - 1) / DEC_DIGITS - 1; 592 else 593 weight = -((-dweight - 1) / DEC_DIGITS + 1); 594 offset = (weight + 1) * DEC_DIGITS - (dweight + 1); 595 ndigits = (ddigits + offset + DEC_DIGITS - 1) / DEC_DIGITS; 596 597 dest->digits = (NumericDigit *) malloc((ndigits) * sizeof(NumericDigit)); 598 dest->ndigits = ndigits; 599 dest->sign = sign; 600 dest->weight = weight; 601 dest->dscale = dscale; 602 603 i = DEC_DIGITS - offset; 604 digits = dest->digits; 605 606 while (ndigits-- > 0) 607 { 608 *digits++ = ((decdigits[i] * 10 + decdigits[i + 1]) * 10 + 609 decdigits[i + 2]) * 10 + decdigits[i + 3]; 610 i += DEC_DIGITS; 611 } 612 613 free(decdigits); 614 615 /* Strip any leading/trailing zeroes, and normalize weight if zero */ 616 strip_var(dest); 617 return 0; 618 } 619 620 /* 621 * num2str() - 622 * 623 * Convert a var to text representation (guts of numeric_out). 624 * CAUTION: var's contents may be modified by rounding! 625 * returns -1 on error and 0 for success. 626 */ 627 static int 628 num2str(char *out, size_t outl, NumericVar *var, int dscale) 629 { 630 //char *str; 631 char *cp; 632 char *endcp; 633 int i; 634 int d; 635 NumericDigit dig; 636 NumericDigit d1; 637 638 if (dscale < 0) 639 dscale = 0; 640 641 /* 642 * Check if we must round up before printing the value and do so. 643 */ 644 round_var(var, dscale); 645 646 /* 647 * Allocate space for the result. 648 * 649 * i is set to to # of decimal digits before decimal point. dscale is the 650 * # of decimal digits we will print after decimal point. We may generate 651 * as many as DEC_DIGITS-1 excess digits at the end, and in addition we 652 * need room for sign, decimal point, null terminator. 653 */ 654 i = (var->weight + 1) * DEC_DIGITS; 655 if (i <= 0) 656 i = 1; 657 658 if (outl <= (size_t) (i + dscale + DEC_DIGITS + 2)) 659 return -1; 660 661 //str = palloc(i + dscale + DEC_DIGITS + 2); 662 //cp = str 663 cp = out; 664 665 /* 666 * Output a dash for negative values 667 */ 668 if (var->sign == NUMERIC_NEG) 669 *cp++ = '-'; 670 671 /* 672 * Output all digits before the decimal point 673 */ 674 if (var->weight < 0) 675 { 676 d = var->weight + 1; 677 *cp++ = '0'; 678 } 679 else 680 { 681 for (d = 0; d <= var->weight; d++) 682 { 683 dig = (d < var->ndigits) ? var->digits[d] : 0; 684 /* In the first digit, suppress extra leading decimal zeroes */ 685 { 686 int putit = (d > 0); 687 688 d1 = dig / 1000; 689 dig -= d1 * 1000; 690 putit |= (d1 > 0); 691 if (putit) 692 *cp++ = (char) (d1 + '0'); 693 d1 = dig / 100; 694 dig -= d1 * 100; 695 putit |= (d1 > 0); 696 if (putit) 697 *cp++ = (char) (d1 + '0'); 698 d1 = dig / 10; 699 dig -= d1 * 10; 700 putit |= (d1 > 0); 701 if (putit) 702 *cp++ = (char) (d1 + '0'); 703 *cp++ = (char) (dig + '0'); 704 } 705 } 706 } 707 708 /* 709 * If requested, output a decimal point and all the digits that follow it. 710 * We initially put out a multiple of DEC_DIGITS digits, then truncate if 711 * needed. 712 */ 713 if (dscale > 0) 714 { 715 *cp++ = '.'; 716 endcp = cp + dscale; 717 for (i = 0; i < dscale; d++, i += DEC_DIGITS) 718 { 719 dig = (d >= 0 && d < var->ndigits) ? var->digits[d] : 0; 720 d1 = dig / 1000; 721 dig -= d1 * 1000; 722 *cp++ = (char) (d1 + '0'); 723 d1 = dig / 100; 724 dig -= d1 * 100; 725 *cp++ = (char) (d1 + '0'); 726 d1 = dig / 10; 727 dig -= d1 * 10; 728 *cp++ = (char) (d1 + '0'); 729 *cp++ = (char) (dig + '0'); 730 } 731 cp = endcp; 732 } 733 734 /* 735 * terminate the string and return it 736 */ 737 *cp = '\0'; 738 return 0; 739 } |