📄 numeric.c
字号:
/* FreeTDS - Library of routines accessing Sybase and Microsoft databases * Copyright (C) 1998-1999 Brian Bruns * Copyright (C) 2005 Frediano Ziglio * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */#if HAVE_CONFIG_H#include <config.h>#endif#include <stdarg.h>#include <stdio.h>#if HAVE_STRING_H#include <string.h>#endif /* HAVE_STRING_H */#include <tds.h>#include <tdsconvert.h>#include <tdsbytes.h>#include <stdlib.h>#ifdef DMALLOC#include <dmalloc.h>#endifTDS_RCSID(var, "$Id: numeric.c,v 1.42 2006/12/26 14:56:21 freddy77 Exp $");/* * these routines use arrays of unsigned char to handle arbitrary * precision numbers. All-in-all it's probably pretty slow, but it * does work. I just heard of a GNU lib for arb. precision math, so * that might be an option in the future. */#ifndef HAVE_INT64static int multiply_byte(unsigned char *product, int num, unsigned char *multiplier);static int do_carry(unsigned char *product);static char *array_to_string(unsigned char *array, int scale, char *s);#endif/** * tds_numeric_bytes_per_prec is indexed by precision and will * tell us the number of bytes required to store the specified * precision (with the sign). * Support precision up to 77 digits */const int tds_numeric_bytes_per_prec[] = { /* * precision can't be 0 but using a value > 0 assure no * core if for some bug it's 0... */ 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 6, 6, 6, 7, 7, 8, 8, 9, 9, 9, 10, 10, 11, 11, 11, 12, 12, 13, 13, 14, 14, 14, 15, 15, 16, 16, 16, 17, 17, 18, 18, 19, 19, 19, 20, 20, 21, 21, 21, 22, 22, 23, 23, 24, 24, 24, 25, 25, 26, 26, 26, 27, 27, 28, 28, 28, 29, 29, 30, 30, 31, 31, 31, 32, 32, 33, 33, 33};/* * money is a special case of numeric really...that why its here */char *tds_money_to_string(const TDS_MONEY * money, char *s){#ifdef HAVE_INT64 int frac; TDS_INT8 mymoney; TDS_UINT8 n; char *p; /* sometimes money it's only 4-byte aligned so always compute 64-bit */ /* FIXME align money/double/bigint in row to 64-bit */ mymoney = (((TDS_INT8)(((TDS_INT*)money)[0])) << 32) | ((TDS_UINT*) money)[1]; p = s; if (mymoney < 0) { *p++ = '-'; /* we use unsigned cause this cause arithmetic problem for -2^63*/ n = -mymoney; } else { n = mymoney; } n = (n + 50) / 100; frac = (int) (n % 100); n /= 100; /* if machine is 64 bit you do not need to split n */#if defined(TDS_I64_FORMAT) sprintf(p, "%" TDS_I64_FORMAT ".%02d", n, frac);#elif SIZEOF_LONG < 8 if (n >= 1000000000) { sprintf(p, "%ld%09ld.%02d", (long)(n / 1000000000), (long)(n % 1000000000), frac); } else sprintf(p, "%ld.%02d", (long)n, frac);#else sprintf(p, "%ld.%02d", (long)n, frac);#endif return s;#else unsigned char multiplier[MAXPRECISION], temp[MAXPRECISION]; unsigned char product[MAXPRECISION]; const unsigned char *number; unsigned char tmpnumber[8]; int i, num_bytes = 8; int pos, neg = 0; memset(multiplier, 0, MAXPRECISION); memset(product, 0, MAXPRECISION); multiplier[0] = 1; number = (const unsigned char *) money;#ifdef WORDS_BIGENDIAN /* big endian makes things easy */ memcpy(tmpnumber, number, 8);#else /* * money is two 32 bit ints and thus is out of order on * little endian machines. Proof of the superiority of * big endian design. ;) */ for (i = 0; i < 4; i++) tmpnumber[3 - i] = number[i]; for (i = 4; i < 8; i++) tmpnumber[7 - i + 4] = number[i];#endif if (tmpnumber[0] & 0x80) { /* negative number -- preform two's complement */ neg = 1; for (i = 0; i < 8; i++) { tmpnumber[i] = ~tmpnumber[i]; } for (i = 7; i >= 0; i--) { tmpnumber[i] += 1; if (tmpnumber[i] != 0) break; } } for (pos = num_bytes - 1; pos >= 0; pos--) { multiply_byte(product, tmpnumber[pos], multiplier); memcpy(temp, multiplier, MAXPRECISION); memset(multiplier, 0, MAXPRECISION); multiply_byte(multiplier, 256, temp); } if (neg) { s[0] = '-'; array_to_string(product, 4, &s[1]); } else { array_to_string(product, 4, s); } /* round to two decimal places */ if (s) { sprintf(s, "%.02f", atof(s)); } return s;#endif}#ifndef HAVE_INT64static intmultiply_byte(unsigned char *product, int num, unsigned char *multiplier){ unsigned char number[3]; int i, top, j, start; number[0] = num % 10; number[1] = (num / 10) % 10; number[2] = (num / 100) % 10; for (top = MAXPRECISION - 1; top >= 0 && !multiplier[top]; top--); start = 0; for (i = 0; i <= top; i++) { for (j = 0; j < 3; j++) { product[j + start] += multiplier[i] * number[j]; } do_carry(product); start++; } return 0;}static intdo_carry(unsigned char *product){ int j; for (j = 0; j < MAXPRECISION; j++) { if (product[j] > 9) { product[j + 1] += product[j] / 10; product[j] = product[j] % 10; } } return 0;}static char *array_to_string(unsigned char *array, int scale, char *s){ int top, i, j; for (top = MAXPRECISION - 1; top >= 0 && top > scale && !array[top]; top--); if (top == -1) { s[0] = '0'; s[1] = '\0'; return s; } j = 0; for (i = top; i >= 0; i--) { if (top + 1 - j == scale) s[j++] = '.'; s[j++] = array[i] + '0'; } s[j] = '\0'; return s;}#endifTDS_INTtds_numeric_to_string(const TDS_NUMERIC * numeric, char *s){ const unsigned char *number; unsigned int packet[sizeof(numeric->array) / 2]; unsigned int *pnum, *packet_start; unsigned int *const packet_end = packet + TDS_VECTOR_SIZE(packet); unsigned int packet10k[(MAXPRECISION + 3) / 4]; unsigned int *p; int num_bytes; unsigned int remainder, n, i, m; /* a bit of debug */#if ENABLE_EXTRA_CHECKS memset(packet, 0x55, sizeof(packet)); memset(packet10k, 0x55, sizeof(packet10k));#endif if (numeric->precision < 1 || numeric->precision > MAXPRECISION || numeric->scale > numeric->precision) return TDS_CONVERT_FAIL; /* set sign */ if (numeric->array[0] == 1) *s++ = '-'; /* put number in a 16bit array */ number = numeric->array; num_bytes = tds_numeric_bytes_per_prec[numeric->precision]; n = num_bytes - 1; pnum = packet_end; for (; n > 1; n -= 2) *--pnum = TDS_GET_UA2BE(&number[n - 1]); if (n == 1) *--pnum = number[n]; while (!*pnum) { ++pnum; if (pnum == packet_end) { *s++ = '0'; if (numeric->scale) { *s++ = '.'; i = numeric->scale; do { *s++ = '0'; } while (--i); } *s++ = 0; return TDS_SUCCEED; } } packet_start = pnum; /* transform 2^16 base number in 10^4 base number */ for (p = packet10k + TDS_VECTOR_SIZE(packet10k); packet_start != packet_end;) { pnum = packet_start; n = *pnum; remainder = n % 10000u; if (!(*pnum++ = (n / 10000u))) packet_start = pnum; for (; pnum != packet_end; ++pnum) { n = remainder * (256u * 256u) + *pnum; remainder = n % 10000u; *pnum = n / 10000u; } *--p = remainder; } /* transform to 10 base number and output */ i = 4 * ((packet10k + TDS_VECTOR_SIZE(packet10k)) - p); /* current digit */ /* skip leading zeroes */ n = 1000; remainder = *p; while (remainder < n) n /= 10, --i; if (i <= numeric->scale) { *s++ = '0'; *s++ = '.'; m = i; while (m < numeric->scale) *s++ = '0', ++m; } for (;;) { *s++ = (remainder / n) + '0'; --i; remainder %= n; n /= 10; if (!n) { n = 1000; if (++p == packet10k + TDS_VECTOR_SIZE(packet10k)) break; remainder = *p; } if (i == numeric->scale) *s++ = '.'; } *s++ = 0; return TDS_SUCCEED;}#ifndef HAVE_INT64#define TDS_WORD TDS_USMALLINT#define TDS_DWORD TDS_UINT#define TDS_WORD_DDIGIT 4#else#define TDS_WORD TDS_UINT#define TDS_DWORD TDS_UINT8#define TDS_WORD_DDIGIT 9#endif/* include to check limits */#include "num_limits.h"static inttds_packet_check_overflow(TDS_WORD *packet, unsigned int packet_len, unsigned int prec){ unsigned int i; int l, stop; const TDS_WORD *limit = &limits[limit_indexes[prec] + LIMIT_INDEXES_ADJUST * prec]; l = limit_indexes[prec+1] - limit_indexes[prec] + LIMIT_INDEXES_ADJUST; stop = prec / (sizeof(TDS_WORD) * 8); /* * Now a number is * ... P[3] P[2] P[1] P[0] * while upper limit + 1 is * zeroes limit[0 .. l-1] 0[0 .. stop-1] * we must assure that number < upper limit + 1 */ if (packet_len >= l + stop) { /* higher packets must be zero */ for (i = packet_len; --i >= l + stop; ) if (packet[i] > 0) return TDS_CONVERT_OVERFLOW; /* test limit */ for (;; --i, ++limit) { if (i <= stop) { /* last must be >= not > */ if (packet[i] >= *limit) return TDS_CONVERT_OVERFLOW; break; } if (packet[i] > *limit) return TDS_CONVERT_OVERFLOW; if (packet[i] < *limit) break; } } return 0;}TDS_INTtds_numeric_change_prec_scale(TDS_NUMERIC * numeric, unsigned char new_prec, unsigned char new_scale){ static const TDS_WORD factors[] = { 1, 10, 100, 1000, 10000,#ifdef HAVE_INT64 100000, 1000000, 10000000, 100000000, 1000000000#endif }; TDS_WORD packet[(sizeof(numeric->array) - 1) / sizeof(TDS_WORD)]; unsigned int i, packet_len; int scale_diff, bytes; if (numeric->precision < 1 || numeric->precision > 77 || numeric->scale > numeric->precision) return TDS_CONVERT_FAIL; if (new_prec < 1 || new_prec > 77 || new_scale > new_prec) return TDS_CONVERT_FAIL; scale_diff = new_scale - numeric->scale; if (scale_diff == 0 && new_prec >= numeric->precision) { i = tds_numeric_bytes_per_prec[new_prec] - tds_numeric_bytes_per_prec[numeric->precision]; if (i > 0) { memmove(numeric->array + 1 + i, numeric->array + 1, sizeof(numeric->array) - 1 - i); memset(numeric->array + 1, 0, i); } numeric->precision = new_prec; return sizeof(TDS_NUMERIC); } /* package number */ bytes = tds_numeric_bytes_per_prec[numeric->precision] - 1; i = 0; do { /* * note that if bytes are smaller we have a small buffer * overflow in numeric->array however is not a problem * cause overflow occurs in numeric and number is fixed below */#ifndef HAVE_INT64 packet[i] = TDS_GET_UA2BE(&numeric->array[bytes-1]);#else packet[i] = TDS_GET_UA4BE(&numeric->array[bytes-3]);#endif ++i; } while ( (bytes -= sizeof(TDS_WORD)) > 0); /* fix last packet */ if (bytes < 0) packet[i-1] &= 0xffffffffu >> (8 * -bytes); while (i > 1 && packet[i-1] == 0) --i; packet_len = i; if (scale_diff >= 0) { /* check overflow before multiply */ if (tds_packet_check_overflow(packet, packet_len, new_prec - scale_diff)) return TDS_CONVERT_OVERFLOW; if (scale_diff == 0) { i = tds_numeric_bytes_per_prec[numeric->precision] - tds_numeric_bytes_per_prec[new_prec]; if (i > 0) memmove(numeric->array + 1, numeric->array + 1 + i, sizeof(numeric->array) - 1 - i); numeric->precision = new_prec; return sizeof(TDS_NUMERIC); } /* multiply */ do { /* multiply by at maximun TDS_WORD_DDIGIT */ unsigned int n = scale_diff > TDS_WORD_DDIGIT ? TDS_WORD_DDIGIT : scale_diff; TDS_WORD factor = factors[n]; TDS_WORD carry = 0; scale_diff -= n; for (i = 0; i < packet_len; ++i) { TDS_DWORD n = packet[i] * ((TDS_DWORD) factor) + carry; packet[i] = (TDS_WORD) n; carry = n >> (8 * sizeof(TDS_WORD)); } /* here we can expand number safely cause we know that it can't overflow */ if (carry) packet[packet_len++] = carry; } while (scale_diff > 0); } else { /* check overflow */ if (new_prec - scale_diff < numeric->precision) if (tds_packet_check_overflow(packet, packet_len, new_prec - scale_diff)) return TDS_CONVERT_OVERFLOW; /* divide */ scale_diff = -scale_diff; do { unsigned int n = scale_diff > TDS_WORD_DDIGIT ? TDS_WORD_DDIGIT : scale_diff; TDS_WORD factor = factors[n]; TDS_WORD borrow = 0; scale_diff -= n; for (i = packet_len; i > 0; ) {#if defined(__GNUC__) && __GNUC__ >= 3 && defined(__i386__) && defined(HAVE_INT64) --i; __asm__ __volatile__ ("divl %4": "=a"(packet[i]), "=d"(borrow): "0"(packet[i]), "1"(borrow), "r"(factor));#elif defined(__WATCOMC__) && defined(DOS32X) TDS_WORD Int64div32(TDS_WORD* low,TDS_WORD high,TDS_WORD factor); #pragma aux Int64div32 = "mov eax, dword ptr[esi]" \ "div ecx" \ "mov dword ptr[esi], eax" \ parm [ESI] [EDX] [ECX] value [EDX] modify [EAX EDX]; borrow = Int64div32(&packet[i], borrow, factor);#else TDS_DWORD n = (((TDS_DWORD) borrow) << (8 * sizeof(TDS_WORD))) + packet[--i]; packet[i] = n / factor; borrow = n % factor;#endif } } while (scale_diff > 0); } /* back to our format */ numeric->precision = new_prec; numeric->scale = new_scale; bytes = tds_numeric_bytes_per_prec[numeric->precision] - 1; for (i = bytes / sizeof(TDS_WORD); i >= packet_len; --i) packet[i] = 0; for (i = 0; bytes >= sizeof(TDS_WORD); bytes -= sizeof(TDS_WORD), ++i) {#ifndef HAVE_INT64 TDS_PUT_UA2BE(&numeric->array[bytes-1], packet[i]);#else TDS_PUT_UA4BE(&numeric->array[bytes-3], packet[i]);#endif } if (bytes) { TDS_WORD remainder = packet[i]; do { numeric->array[bytes] = (TDS_UCHAR) remainder; remainder >>= 8; } while (--bytes); } return sizeof(TDS_NUMERIC);}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -