commit fd0aaec61a199fffea75579f7aea821f8094374c
Author: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date:   Tue Jun 10 13:35:05 2014 +0300

    Rewrite the conversion functions between strings and SQL_NUMERIC_STRUCT.
    
    Also adds a test case for the conversions.

diff --git a/convert.c b/convert.c
index 8871216..ae18250 100644
--- a/convert.c
+++ b/convert.c
@@ -215,6 +215,8 @@ static unsigned ODBCINT64 ATOI64U(const char *val)
 #endif /* WIN32 */
 #endif /* ODBCINT64 */
 
+static void parse_to_numeric_struct(const char *wv, SQL_NUMERIC_STRUCT *ns, BOOL *overflow);
+
 /*
  *	TIMESTAMP <-----> SIMPLE_TIME
  *		precision support since 7.2.
@@ -1595,80 +1597,21 @@ inolog("2stime fr=%d\n", std_time.fr);
 				break;
 
 #if (ODBCVER >= 0x0300)
-                        case SQL_C_NUMERIC:
-			{
-			SQL_NUMERIC_STRUCT      *ns;
-			int	i, nlen, bit, hval, tv, dig, sta, olen;
-			char	calv[SQL_MAX_NUMERIC_LEN * 3];
-			const char *wv;
-			BOOL	dot_exist;
-
-			len = sizeof(SQL_NUMERIC_STRUCT);
-			if (bind_size > 0)
-				ns = (SQL_NUMERIC_STRUCT *) rgbValueBindRow;
-			else
-				ns = (SQL_NUMERIC_STRUCT *) rgbValue + bind_row;
-			for (wv = neut_str; *wv && isspace((unsigned char) *wv); wv++)
-				;
-			ns->sign = 1;
-			if (*wv == '-')
-			{
-				ns->sign = 0;
-				wv++;
-			}
-			else if (*wv == '+')
-				wv++;
-			while (*wv == '0') wv++;
-			ns->precision = 0;
-			ns->scale = 0;
-			for (nlen = 0, dot_exist = FALSE;; wv++)
-			{
-				if (*wv == '.')
-				{
-					if (dot_exist)
-						break;
-					dot_exist = TRUE;
-				}
-				else if (*wv == '\0' || !isdigit((unsigned char) *wv))
-						break;
-				else
-				{
-					if (dot_exist)
-						ns->scale++;
-					ns->precision++;
-					calv[nlen++] = *wv;
-				}
-			}
-			memset(ns->val, 0, sizeof(ns->val));
-			for (hval = 0, bit = 1L, sta = 0, olen = 0; sta < nlen;)
-			{
-				for (dig = 0, i = sta; i < nlen; i++)
+			case SQL_C_NUMERIC:
 				{
-					tv = dig * 10 + calv[i] - '0';
-					dig = tv % 2;
-					calv[i] = tv / 2 + '0';
-					if (i == sta && tv < 2)
-						sta++;
-				}
-				if (dig > 0)
-					hval |= bit;
-				bit <<= 1;
-				if (bit >= (1L << 8))
-				{
-					ns->val[olen++] = hval;
-					hval = 0;
-					bit = 1L;
-					if (olen >= SQL_MAX_NUMERIC_LEN - 1)
-					{
-						ns->scale = sta - ns->precision;
-						break;
-					}
+					SQL_NUMERIC_STRUCT      *ns;
+					BOOL	overflowed;
+
+					if (bind_size > 0)
+						ns = (SQL_NUMERIC_STRUCT *) rgbValueBindRow;
+					else
+						ns = (SQL_NUMERIC_STRUCT *) rgbValue + bind_row;
+
+					parse_to_numeric_struct(neut_str, ns, &overflowed);
+					if (overflowed)
+						result = COPY_RESULT_TRUNCATED;
 				}
-			}
-			if (hval && olen < SQL_MAX_NUMERIC_LEN - 1)
-				ns->val[olen++] = hval;
-			}
-			break;
+				break;
 #endif /* ODBCVER */
 
 			case SQL_C_SSHORT:
@@ -3829,136 +3772,201 @@ cleanup:
 }
 
 #if (ODBCVER >= 0x0300)
-static BOOL
+
+/*
+ * With SQL_MAX_NUMERIC_LEN = 16, the highest representable number is
+ * 2^128 - 1, which fits in 39 digits.
+ */
+#define MAX_NUMERIC_DIGITS 39
+
+/*
+ * Convert a SQL_NUMERIC_STRUCT into string representation.
+ */
+static void
 ResolveNumericParam(const SQL_NUMERIC_STRUCT *ns, char *chrform)
 {
-	static const int prec[] = {1, 3, 5, 8, 10, 13, 15, 17, 20, 22, 25, 27, 29, 32, 34, 37, 39};
-	Int4	i, j, k, ival, vlen, len, newlen;
-	UCHAR		calv[40];
+	Int4		i, vlen, len, newlen;
 	const UCHAR	*val = (const UCHAR *) ns->val;
-	BOOL	next_figure;
+	UCHAR		vals[SQL_MAX_NUMERIC_LEN];
+	int			lastnonzero;
+	UCHAR		calv[MAX_NUMERIC_DIGITS];
+	int			precision;
 
 inolog("C_NUMERIC [prec=%d scale=%d]", ns->precision, ns->scale);
+
 	if (0 == ns->precision)
 	{
 		strcpy(chrform, "0");
-		return TRUE;
-	}
-	else if (ns->precision < prec[sizeof(Int4)])
-	{
-		for (i = 0, ival = 0; i < sizeof(Int4) && prec[i] <= ns->precision; i++)
-		{
-inolog("(%d)", val[i]);
-			ival += (val[i] << (8 * i)); /* ns->val is little endian */
-		}
-inolog(" ival=%d,%d", ival, (val[3] << 24) | (val[2] << 16) | (val[1] << 8) | val[0]);
-		if (0 == ns->scale)
-		{
-			if (0 == ns->sign)
-				ival *= -1;
-			sprintf(chrform, "%d", ival);
-		}
-		else if (ns->scale > 0)
-		{
-			Int4	i, div, o1val, o2val;
-
-			for (i = 0, div = 1; i < ns->scale; i++)
-				div *= 10;
-			o1val = ival / div;
-			o2val = ival % div;
-			if (0 == ns->sign)
-				sprintf(chrform, "-%d.%0*d", o1val, ns->scale, o2val);
-			else
-				sprintf(chrform, "%d.%0*d", o1val, ns->scale, o2val);
-		}
-inolog(" convval=%s\n", chrform);
-		return TRUE;
+		return;
 	}
 
-	for (i = 0; i < SQL_MAX_NUMERIC_LEN && prec[i] <= ns->precision; i++)
-		;
-	vlen = i;
+	precision = ns->precision;
+	if (precision > MAX_NUMERIC_DIGITS)
+		precision = MAX_NUMERIC_DIGITS;
+
+	/*
+	 * The representation in SQL_NUMERIC_STRUCT is 16 bytes with most
+	 * significant byte first. Make a working copy.
+	 */
+	memcpy(vals, val, SQL_MAX_NUMERIC_LEN);
+
+	vlen = SQL_MAX_NUMERIC_LEN;
 	len = 0;
-	memset(calv, 0, sizeof(calv));
-inolog(" len1=%d", vlen);
-	for (i = vlen - 1; i >= 0; i--)
+	do
 	{
-		for (j = len - 1; j >= 0; j--)
-		{
-			if (!calv[j])
-				continue;
-			ival = (((Int4)calv[j]) << 8);
-			calv[j] = (ival % 10);
-			ival /= 10;
-			calv[j + 1] += (ival % 10);
-			ival /= 10;
-			calv[j + 2] += (ival % 10);
-			ival /= 10;
-			calv[j + 3] += ival;
-			for (k = j;; k++)
-			{
-				next_figure = FALSE;
-				if (calv[k] > 0)
-				{
-					if (k >= len)
-						len = k + 1;
-					while (calv[k] > 9)
-					{
-						calv[k + 1]++;
-						calv[k] -= 10;
-						next_figure = TRUE;
-					}
-				}
-				if (k >= j + 3 && !next_figure)
-					break;
-			}
-		}
-		ival = val[i];
-		if (!ival)
-			continue;
-		calv[0] += (ival % 10);
-		ival /= 10;
-		calv[1] += (ival % 10);
-		ival /= 10;
-		calv[2] += ival;
-		for (j = 0;; j++)
+		UInt2		d, r;
+
+		/*
+		 * Divide the number by 10, and output the reminder as the next digit.
+		 *
+		 * Begin from the most-significant byte (last in the array), and at
+		 * each step, carry the remainder to the prev byte.
+		 */
+		r = 0;
+		lastnonzero = -1;
+		for (i = vlen - 1; i >= 0; i--)
 		{
-			next_figure = FALSE;
-			if (calv[j] > 0)
-			{
-				if (j >= len)
-					len = j + 1;
-				while (calv[j] > 9)
-				{
-					calv[j + 1]++;
-					calv[j] -= 10;
-					next_figure = TRUE;
-				}
-			}
-			if (j >= 2 && !next_figure)
-				break;
+			UInt2	v;
+
+			v = ((UInt2) vals[i]) + (r << 8);
+			d = v / 10; r = v % 10;
+			vals[i] = (UCHAR) d;
+
+			if (d != 0 && lastnonzero == -1)
+				lastnonzero = i;
 		}
-	}
+
+		/* output the remainder */
+		calv[len++] = r;
+
+		vlen = lastnonzero + 1;
+	} while(lastnonzero >= 0 && len < precision);
+
+	/*
+	 * calv now contains the digits in reverse order, i.e. least significant
+	 * digit is at calv[0]
+	 */
+
 inolog(" len2=%d", len);
+
+	/* build the final output string. */
 	newlen = 0;
 	if (0 == ns->sign)
 		chrform[newlen++] = '-';
-	if (i = len - 1, i < ns->scale)
+
+	i = len - 1;
+	if (i < ns->scale)
 		i = ns->scale;
 	for (; i >= ns->scale; i--)
-		chrform[newlen++] = calv[i] + '0';
+	{
+		if (i >= len)
+			chrform[newlen++] = '0';
+		else
+			chrform[newlen++] = calv[i] + '0';
+	}
 	if (ns->scale > 0)
 	{
 		chrform[newlen++] = '.';
 		for (; i >= 0; i--)
-			chrform[newlen++] = calv[i] + '0';
+		{
+			if (i >= len)
+				chrform[newlen++] = '0';
+			else
+				chrform[newlen++] = calv[i] + '0';
+		}
 	}
 	if (0 == len)
 		chrform[newlen++] = '0';
 	chrform[newlen] = '\0';
 inolog(" convval(2) len=%d %s\n", newlen, chrform);
-	return TRUE;
 }
+
+/*
+ * Convert a string representation of a numeric into SQL_NUMERIC_STRUCT.
+ */
+static void
+parse_to_numeric_struct(const char *wv, SQL_NUMERIC_STRUCT *ns, BOOL *overflow)
+{
+	int			i, nlen, dig;
+	char		calv[SQL_MAX_NUMERIC_LEN * 3];
+	BOOL		dot_exist;
+
+	*overflow = FALSE;
+
+	/* skip leading space */
+	while (*wv && isspace((unsigned char) *wv))
+		wv++;
+
+	/* sign */
+	ns->sign = 1;
+	if (*wv == '-')
+	{
+		ns->sign = 0;
+		wv++;
+	}
+	else if (*wv == '+')
+		wv++;
+
+	/* skip leading zeros */
+	while (*wv == '0')
+		wv++;
+
+	/* read the digits into calv */
+	ns->precision = 0;
+	ns->scale = 0;
+	for (nlen = 0, dot_exist = FALSE;; wv++)
+	{
+		if (*wv == '.')
+		{
+			if (dot_exist)
+				break;
+			dot_exist = TRUE;
+		}
+		else if (*wv == '\0' || !isdigit((unsigned char) *wv))
+			break;
+		else
+		{
+			if (nlen >= sizeof(calv))
+			{
+				if (dot_exist)
+					break;
+				else
+				{
+					ns->scale--;
+					*overflow = TRUE;
+					continue;
+				}
+			}
+			if (dot_exist)
+				ns->scale++;
+			calv[nlen++] = *wv;
+		}
+	}
+	ns->precision = nlen;
+
+	/* Convert the decimal digits to binary */
+	memset(ns->val, 0, sizeof(ns->val));
+	for (dig = 0; dig < nlen; dig++)
+	{
+		UInt4 carry;
+
+		/* multiply the current value by 10, and add the next digit */
+		carry = calv[dig] - '0';
+		for (i = 0; i < sizeof(ns->val); i++)
+		{
+			UInt4		t;
+
+			t = ((UInt4) ns->val[i]) * 10 + carry;
+			ns->val[i] = (unsigned char) (t & 0xFF);
+			carry = (t >> 8);
+		}
+
+		if (carry != 0)
+			*overflow = TRUE;
+	}
+}
+
+
 #endif /* ODBCVER */
 
 /*
@@ -3975,7 +3983,7 @@ ResolveOneParam(QueryBuild *qb, QueryParse *qp, BOOL *isnull)
 	PutDataInfo *pdata = qb->pdata;
 
 	int			param_number;
-	char		param_string[128],
+	char		param_string[150],
 				tmp[256];
 	char		cbuf[PG_NUMERIC_MAX_PRECISION * 2]; /* seems big enough to handle the data in this function */
 	OID			param_pgtype;
@@ -4403,8 +4411,10 @@ mylog("C_WCHAR=%s(%d)\n", buffer, used);
 			}
 #if (ODBCVER >= 0x0300)
 		case SQL_C_NUMERIC:
-			if (ResolveNumericParam((SQL_NUMERIC_STRUCT *) buffer, param_string))
-				break;
+		{
+			ResolveNumericParam((SQL_NUMERIC_STRUCT *) buffer, param_string);
+			break;
+		}
 		case SQL_C_INTERVAL_YEAR:
 			ivsign = ivstruct->interval_sign ? "-" : "";
 			sprintf(param_string, "%s%d years", ivsign, ivstruct->intval.year_month.year);
diff --git a/test/Makefile.in b/test/Makefile.in
index 4bb5149..8bc3419 100644
--- a/test/Makefile.in
+++ b/test/Makefile.in
@@ -2,7 +2,7 @@ TESTS = connect stmthandles select commands multistmt getresult prepare \
 	params notice arraybinding insertreturning dataatexecution \
 	boolsaschar cvtnulldate alter quotes cursors positioned-update \
 	catalogfunctions bindcol lfconversion cte deprecated error-rollback \
-	diagnostic
+	diagnostic numeric
 
 TESTBINS = $(patsubst %,src/%-test, $(TESTS))
 TESTSQLS = $(patsubst %,sql/%.sql, $(TESTS))
diff --git a/test/expected/numeric.out b/test/expected/numeric.out
new file mode 100644
index 0000000..e01c6d2
--- /dev/null
+++ b/test/expected/numeric.out
@@ -0,0 +1,61 @@
+\! ./src/numeric-test
+connected
+Testing SQL_NUMERIC_STRUCT params...
+
+sign 1 prec 5 scale 3 val 7C62:
+    25.212
+sign 1 prec 41 scale 0 val 78563412 78563412 78563412 78563412:
+    24197857161011715162171839636988778104
+sign 1 prec 38 scale 0 val 4EF338DE509049C4133302F0F6B04909:
+    12345678901234567890123456789012345678
+sign 1 prec 50 scale 0 val FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF:
+    340282366920938463463374607431768211455
+sign 1 prec 1 scale 0 val 00:
+    0
+sign 0 prec 1 scale 0 val 00:
+    0
+sign 1 prec 3 scale 2 val 0203:
+    7.70
+sign 1 prec 1 scale 0 val 3930:
+    1
+sign 1 prec 2 scale 0 val 3930:
+    12
+sign 1 prec 3 scale 0 val 3930:
+    123
+sign 1 prec 4 scale 0 val 3930:
+    1234
+sign 1 prec 5 scale 0 val 3930:
+    12345
+sign 1 prec 6 scale 0 val 3930:
+    12345
+sign 1 prec 3 scale 50 val 0203:
+    0.00000000000000000000000000000000000000000000000770
+sign 1 prec 25 scale 80 val 0203:
+    0.00000000000000000000000000000000000000000000000000000000000000000000000000000770
+sign 0 prec 25 scale 127 val FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF:
+    -0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003402823669209384634633746
+sign 1 prec 3 scale 50 val 0203:
+    0.00000000000000000000000000000000000000000000000770
+Testing SQL_NUMERIC_STRUCT results...
+
+25.212:
+     sign 1 prec 5 scale 3 val 7C620000000000000000000000000000
+24197857161011715162171839636988778104:
+     sign 1 prec 38 scale 0 val 78563412785634127856341278563412
+12345678901234567890123456789012345678:
+     sign 1 prec 38 scale 0 val 4EF338DE509049C4133302F0F6B04909
+340282366920938463463374607431768211455:
+     sign 1 prec 39 scale 0 val FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+340282366920938463463374607431768211456:
+     sign 1 prec 39 scale 0 val 00000000000000000000000000000000
+340282366920938463463374607431768211457:
+     sign 1 prec 39 scale 0 val 01000000000000000000000000000000
+-0:
+     sign 1 prec 0 scale 0 val 00000000000000000000000000000000
+0:
+     sign 1 prec 0 scale 0 val 00000000000000000000000000000000
+-7.70:
+     sign 0 prec 3 scale 2 val 02030000000000000000000000000000
+999999999999:
+     sign 1 prec 12 scale 0 val FF0FA5D4E80000000000000000000000
+disconnecting
diff --git a/test/src/numeric-test.c b/test/src/numeric-test.c
new file mode 100644
index 0000000..3f34bdc
--- /dev/null
+++ b/test/src/numeric-test.c
@@ -0,0 +1,223 @@
+/*
+ * Test cases for dealing with SQL_NUMERIC_STRUCT
+ */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "common.h"
+
+static unsigned char
+hex_to_int(char c)
+{
+	char		result;
+
+	if (c >= '0' && c <= '9')
+		result = c - '0';
+	else if (c >= 'a' && c <= 'f')
+		result = c - 'a' + 10;
+	else if (c >= 'A' && c <= 'F')
+		result = c - 'A' + 10;
+	else
+	{
+		fprintf(stderr, "invalid hex-encoded numeric value\n");
+		exit(1);
+	}
+	return (unsigned char) result;
+}
+
+static void
+build_numeric_struct(SQL_NUMERIC_STRUCT *numericparam,
+					 unsigned char sign, char *hexstr,
+					 unsigned char precision, unsigned char scale)
+{
+	int			len;
+
+	/* parse the hex-encoded value */
+	memset(numericparam, 0, sizeof(SQL_NUMERIC_STRUCT));
+
+	numericparam->sign = sign;
+	numericparam->precision = precision;
+	numericparam->scale = scale;
+
+	len = 0;
+	while (*hexstr)
+	{
+		if (*hexstr == ' ')
+		{
+			hexstr++;
+			continue;
+		}
+		if (len >= SQL_MAX_NUMERIC_LEN)
+		{
+			fprintf(stderr, "hex-encoded numeric value too long\n");
+			exit(1);
+		}
+		numericparam->val[len] =
+			hex_to_int(*hexstr) << 4 | hex_to_int(*(hexstr + 1));
+		hexstr += 2;
+		len++;
+	}
+}
+
+static void
+test_numeric_param(HSTMT hstmt, unsigned char sign, char *hexval,
+				   unsigned char precision, unsigned char scale)
+{
+	SQL_NUMERIC_STRUCT numericparam;
+	SQLLEN		cbParam1;
+	SQLRETURN	rc;
+	char		buf[200];
+
+	build_numeric_struct(&numericparam, sign, hexval, precision, scale);
+
+	cbParam1 = sizeof(numericparam);
+	rc = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT,
+						  SQL_C_NUMERIC,	/* value type */
+						  SQL_NUMERIC,	/* param type */
+						  0,			/* column size (ignored for SQL_INTERVAL_SECOND) */
+						  0,			/* dec digits */
+						  &numericparam, /* param value ptr */
+						  sizeof(numericparam), /* buffer len (ignored for SQL_C_INTERVAL_SECOND) */
+						  &cbParam1 /* StrLen_or_IndPtr (ignored for SQL_C_INTERVAL_SECOND) */);
+	CHECK_STMT_RESULT(rc, "SQLBindParameter failed", hstmt);
+
+	/* Execute */
+	rc = SQLExecute(hstmt);
+	if (!SQL_SUCCEEDED(rc))
+	{
+		print_diag("SQLExecute failed", SQL_HANDLE_STMT, hstmt);
+    }
+	else
+	{
+		/* print result */
+		rc = SQLFetch(hstmt);
+		CHECK_STMT_RESULT(rc, "SQLFetch failed", hstmt);
+
+		rc = SQLGetData(hstmt, 1, SQL_C_CHAR, buf, sizeof(buf), NULL);
+		CHECK_STMT_RESULT(rc, "SQLGetData failed", hstmt);
+		printf("sign %u prec %u scale %d val %s:\n    %s\n",
+			   sign, precision, scale, hexval, buf);
+	}
+
+	rc = SQLFreeStmt(hstmt, SQL_CLOSE);
+	CHECK_STMT_RESULT(rc, "SQLFreeStmt failed", hstmt);
+}
+
+static void
+test_numeric_result(HSTMT hstmt, char *numstr)
+{
+	char		sql[100];
+	SQL_NUMERIC_STRUCT numericres;
+	SQLRETURN	rc;
+
+	/*
+	 * assume 'numstr' param to be well-formed (we're testing how the
+	 * results come out, not the input handling)
+	 */
+	snprintf(sql, sizeof(sql), "SELECT '%s'::numeric", numstr);
+	rc = SQLExecDirect(hstmt, (SQLCHAR *) sql, SQL_NTS);
+	CHECK_STMT_RESULT(rc, "SQLExecDirect failed", hstmt);
+
+	rc = SQLFetch(hstmt);
+	CHECK_STMT_RESULT(rc, "SQLFetch failed", hstmt);
+
+	rc = SQLGetData(hstmt, 1, SQL_C_NUMERIC, &numericres, sizeof(numericres), NULL);
+	CHECK_STMT_RESULT(rc, "SQLGetData failed", hstmt);
+	printf("%s:\n     sign %u prec %u scale %d val %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X\n",
+		   numstr, numericres.sign, numericres.precision, numericres.scale,
+		   numericres.val[0], numericres.val[1],
+		   numericres.val[2], numericres.val[3],
+		   numericres.val[4], numericres.val[5],
+		   numericres.val[6], numericres.val[7],
+		   numericres.val[8], numericres.val[9],
+		   numericres.val[10], numericres.val[11],
+		   numericres.val[12], numericres.val[13],
+		   numericres.val[14], numericres.val[15]);
+
+	rc = SQLFreeStmt(hstmt, SQL_CLOSE);
+	CHECK_STMT_RESULT(rc, "SQLFreeStmt failed", hstmt);
+}
+
+int main(int argc, char **argv)
+{
+	SQLRETURN	rc;
+	HSTMT		hstmt = SQL_NULL_HSTMT;
+
+	test_connect();
+
+	rc = SQLAllocHandle(SQL_HANDLE_STMT, conn, &hstmt);
+	if (!SQL_SUCCEEDED(rc))
+	{
+		print_diag("failed to allocate stmt handle", SQL_HANDLE_DBC, conn);
+		exit(1);
+	}
+
+	/**** Test binding SQL_NUMERIC_STRUCT params (SQL_C_NUMERIC) ****/
+
+	printf("Testing SQL_NUMERIC_STRUCT params...\n\n");
+
+	rc = SQLPrepare(hstmt, (SQLCHAR *) "SELECT ?::numeric", SQL_NTS);
+	CHECK_STMT_RESULT(rc, "SQLPrepare failed", hstmt);
+
+	/* 25.212 (per Microsoft KB 22831) */
+	test_numeric_param(hstmt, 1, "7C62", 5, 3);
+
+	/* 24197857161011715162171839636988778104 */
+	test_numeric_param(hstmt, 1, "78563412 78563412 78563412 78563412", 41, 0);
+
+	/* 12345678901234567890123456789012345678 */
+	test_numeric_param(hstmt, 1, "4EF338DE509049C4133302F0F6B04909", 38, 0);
+
+	/* highest possible non-scaled: 340282366920938463463374607431768211455 */
+	test_numeric_param(hstmt, 1, "FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF", 50, 0);
+
+	/* positive and negative zero */
+	test_numeric_param(hstmt, 1, "00", 1, 0);
+	test_numeric_param(hstmt, 0, "00", 1, 0);
+
+	/* -7.70 */
+	test_numeric_param(hstmt, 1, "0203", 3, 2);
+
+	/* 0.12345, with 1-6 digit precision: */
+	test_numeric_param(hstmt, 1, "3930", 1, 5);
+	test_numeric_param(hstmt, 1, "3930", 2, 5);
+	test_numeric_param(hstmt, 1, "3930", 3, 5);
+	test_numeric_param(hstmt, 1, "3930", 4, 5);
+	test_numeric_param(hstmt, 1, "3930", 5, 5);
+	test_numeric_param(hstmt, 1, "3930", 6, 5);
+
+	/* large scale with small value */
+	test_numeric_param(hstmt, 1, "0203", 3, 50);
+
+	/* medium-sized scale and precision */
+	test_numeric_param(hstmt, 1, "0203", 25, 80);
+
+	/* max length output; negative with max scale and decimal dot */
+	test_numeric_param(hstmt, 0, "FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF", 25, 127);
+
+	/* large scale with small value */
+	test_numeric_param(hstmt, 1, "0203", 3, 50);
+
+
+	/**** Test fetching SQL_NUMERIC_STRUCT results ****/
+	printf("Testing SQL_NUMERIC_STRUCT results...\n\n");
+
+	test_numeric_result(hstmt, "25.212");
+	test_numeric_result(hstmt, "24197857161011715162171839636988778104");
+	test_numeric_result(hstmt, "12345678901234567890123456789012345678");
+	/* highest number */
+	test_numeric_result(hstmt, "340282366920938463463374607431768211455");
+	/* overflow */
+	test_numeric_result(hstmt, "340282366920938463463374607431768211456");
+	test_numeric_result(hstmt, "340282366920938463463374607431768211457");
+
+	test_numeric_result(hstmt, "-0");
+	test_numeric_result(hstmt, "0");
+	test_numeric_result(hstmt, "-7.70");
+	test_numeric_result(hstmt, "999999999999");
+
+	/* Clean up */
+	test_disconnect();
+
+	return 0;
+}
