From beb9def059f3a56a148aca6c40d13e19fe617283 Mon Sep 17 00:00:00 2001 From: drh Date: Mon, 22 Jun 2020 19:12:23 +0000 Subject: [PATCH 1/4] An extension for doing decimal arithmetic on strings. FossilOrigin-Name: 4c3b85607feb53462ccc8b89bea699fdb132c402eae597afa33cc40a85c32329 --- Makefile.in | 2 + Makefile.msc | 2 + ext/misc/decimal.c | 567 +++++++++++++++++++++++++++++++++++++++++++++ main.mk | 2 + manifest | 22 +- manifest.uuid | 2 +- src/shell.c.in | 2 + src/test1.c | 2 + test/decimal.test | 118 ++++++++++ 9 files changed, 711 insertions(+), 8 deletions(-) create mode 100644 ext/misc/decimal.c create mode 100644 test/decimal.test diff --git a/Makefile.in b/Makefile.in index d563849851..081d29482b 100644 --- a/Makefile.in +++ b/Makefile.in @@ -443,6 +443,7 @@ TESTSRC += \ $(TOP)/ext/misc/carray.c \ $(TOP)/ext/misc/closure.c \ $(TOP)/ext/misc/csv.c \ + $(TOP)/ext/misc/decimal.c \ $(TOP)/ext/misc/eval.c \ $(TOP)/ext/misc/explain.c \ $(TOP)/ext/misc/fileio.c \ @@ -1082,6 +1083,7 @@ SHELL_SRC = \ $(TOP)/src/shell.c.in \ $(TOP)/ext/misc/appendvfs.c \ $(TOP)/ext/misc/shathree.c \ + $(TOP)/ext/misc/decimal.c \ $(TOP)/ext/misc/fileio.c \ $(TOP)/ext/misc/completion.c \ $(TOP)/ext/misc/sqlar.c \ diff --git a/Makefile.msc b/Makefile.msc index 241961e2d5..ca643d0842 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -1560,6 +1560,7 @@ TESTEXT = \ $(TOP)\ext\misc\carray.c \ $(TOP)\ext\misc\closure.c \ $(TOP)\ext\misc\csv.c \ + $(TOP)\ext\misc\decimal.c \ $(TOP)\ext\misc\eval.c \ $(TOP)\ext\misc\explain.c \ $(TOP)\ext\misc\fileio.c \ @@ -2204,6 +2205,7 @@ SHELL_SRC = \ $(TOP)\src\shell.c.in \ $(TOP)\ext\misc\appendvfs.c \ $(TOP)\ext\misc\shathree.c \ + $(TOP)\ext\misc\decimal.c \ $(TOP)\ext\misc\fileio.c \ $(TOP)\ext\misc\completion.c \ $(TOP)\ext\misc\uint.c \ diff --git a/ext/misc/decimal.c b/ext/misc/decimal.c new file mode 100644 index 0000000000..078e91dc27 --- /dev/null +++ b/ext/misc/decimal.c @@ -0,0 +1,567 @@ +/* +** 2020-06-22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** Routines to implement arbitrary-precision decimal math. +** +** The focus here is on simplicity and correctness, not performance. +*/ +#include "sqlite3ext.h" +SQLITE_EXTENSION_INIT1 +#include +#include +#include +#include + + +/* A decimal object */ +typedef struct Decimal Decimal; +struct Decimal { + char sign; /* 0 for positive, 1 for negative */ + char oom; /* True if an OOM is encountered */ + char isNull; /* True if holds a NULL rather than a number */ + char isInit; /* True upon initialization */ + int nDigit; /* Total number of digits */ + int nFrac; /* Number of digits to the right of the decimal point */ + signed char *a; /* Array of digits. Most significant first. */ +}; + +/* +** Release memory held by a Decimal, but do not free the object itself. +*/ +static void decimal_clear(Decimal *p){ + sqlite3_free(p->a); +} + +/* +** Destroy a Decimal object +*/ +static void decimal_free(Decimal *p){ + if( p ){ + decimal_clear(p); + sqlite3_free(p); + } +} + +/* +** Allocate a new Decimal object. Initialize it to the number given +** by the input string. +*/ +static Decimal *decimal_new( + sqlite3_context *pCtx, + sqlite3_value *pIn, + int nAlt, + const unsigned char *zAlt +){ + Decimal *p; + int n, i; + const unsigned char *zIn; + int iExp = 0; + p = sqlite3_malloc( sizeof(*p) ); + if( p==0 ) goto new_no_mem; + p->sign = 0; + p->oom = 0; + p->isInit = 1; + p->isNull = 0; + p->nDigit = 0; + p->nFrac = 0; + if( zAlt ){ + n = nAlt, + zIn = zAlt; + }else{ + if( sqlite3_value_type(pIn)==SQLITE_NULL ){ + p->a = 0; + p->isNull = 1; + return p; + } + n = sqlite3_value_bytes(pIn); + zIn = sqlite3_value_text(pIn); + } + p->a = sqlite3_malloc64( n+1 ); + if( p->a==0 ) goto new_no_mem; + for(i=0; isspace(zIn[i]); i++){} + if( zIn[i]=='-' ){ + p->sign = 1; + i++; + }else if( zIn[i]=='+' ){ + i++; + } + while( i='0' && c<='9' ){ + p->a[p->nDigit++] = c - '0'; + }else if( c=='.' ){ + p->nFrac = p->nDigit + 1; + }else if( c=='e' || c=='E' ){ + int j = i+1; + int neg = 0; + if( j>=n ) break; + if( zIn[j]=='-' ){ + neg = 1; + j++; + }else if( zIn[j]=='+' ){ + j++; + } + while( j='0' && zIn[j]<='9' ){ + iExp = iExp*10 + zIn[j] - '0'; + } + j++; + } + if( neg ) iExp = -iExp; + break; + } + i++; + } + if( p->nFrac ){ + p->nFrac = p->nDigit - (p->nFrac - 1); + } + if( iExp>0 ){ + if( p->nFrac>0 ){ + if( iExp<=p->nFrac ){ + p->nFrac -= iExp; + iExp = 0; + }else{ + iExp -= p->nFrac; + p->nFrac = 0; + } + } + if( iExp>0 ){ + p->a = sqlite3_realloc64(p->a, p->nDigit + iExp + 1 ); + if( p->a==0 ) goto new_no_mem; + memset(p->a+p->nDigit, 0, iExp); + p->nDigit += iExp; + } + }else if( iExp<0 ){ + int nExtra; + iExp = -iExp; + nExtra = p->nDigit - p->nFrac - 1; + if( nExtra ){ + if( nExtra>=iExp ){ + p->nFrac += iExp; + iExp = 0; + }else{ + iExp -= nExtra; + p->nFrac = p->nDigit - 1; + } + } + if( iExp>0 ){ + p->a = sqlite3_realloc64(p->a, p->nDigit + iExp + 1 ); + if( p->a==0 ) goto new_no_mem; + memmove(p->a+iExp, p->a, p->nDigit); + memset(p->a, 0, iExp); + p->nDigit += iExp; + p->nFrac += iExp; + } + } + return p; + +new_no_mem: + if( pCtx ) sqlite3_result_error_nomem(pCtx); + sqlite3_free(p); + return 0; +} + +/* +** Make the given Decimal the result. +*/ +static void decimal_result(sqlite3_context *pCtx, Decimal *p){ + char *z; + int i, j; + int n; + if( p==0 || p->oom ){ + sqlite3_result_error_nomem(pCtx); + return; + } + if( p->isNull ){ + sqlite3_result_null(pCtx); + return; + } + z = sqlite3_malloc( p->nDigit+4 ); + if( z==0 ){ + sqlite3_result_error_nomem(pCtx); + return; + } + i = 0; + if( p->nDigit==0 || (p->nDigit==1 && p->a[0]==0) ){ + p->sign = 0; + } + if( p->sign ){ + z[0] = '-'; + i = 1; + } + n = p->nDigit - p->nFrac; + if( n<=0 ){ + z[i++] = '0'; + } + j = 0; + while( n>0 ){ + z[i++] = p->a[j] + '0'; + j++; + n--; + } + if( p->nFrac ){ + z[i++] = '.'; + do{ + z[i++] = p->a[j] + '0'; + j++; + }while( jnDigit ); + } + z[i] = 0; + sqlite3_result_text(pCtx, z, i, sqlite3_free); +} + +/* +** SQL Function: decimal(X) +** +** Convert input X into decimal and then back into text +*/ +static void decimalFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *p = decimal_new(context, argv[0], 0, 0); + decimal_result(context, p); + decimal_free(p); +} + +/* +** Compare to Decimal objects. Return negative, 0, or positive if the +** first object is less than, equal to, or greater than the second. +** +** Preconditions for this routine: +** +** pA!=0 +** pA->isNull==0 +** pB!=0 +** pB->isNull==0 +*/ +static int decimal_cmp(const Decimal *pA, const Decimal *pB){ + int nASig, nBSig, rc, n; + if( pA->sign!=pB->sign ){ + return pA->sign ? -1 : +1; + } + if( pA->sign ){ + const Decimal *pTemp = pA; + pA = pB; + pB = pTemp; + } + nASig = pA->nDigit - pA->nFrac; + nBSig = pB->nDigit - pB->nFrac; + if( nASig!=nBSig ){ + return nASig - nBSig; + } + n = pA->nDigit; + if( n>pB->nDigit ) n = pB->nDigit; + rc = memcmp(pA->a, pB->a, n); + if( rc==0 ){ + rc = pA->nDigit - pB->nDigit; + } + return rc; +} + +/* +** SQL Function: decimal_cmp(X, Y) +** +** Return negative, zero, or positive if X is less then, equal to, or +** greater than Y. +*/ +static void decimalCmpFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *pA = 0, *pB = 0; + int rc; + + pA = decimal_new(context, argv[0], 0, 0); + if( pA==0 || pA->isNull ) goto cmp_done; + pB = decimal_new(context, argv[1], 0, 0); + if( pB==0 || pB->isNull ) goto cmp_done; + rc = decimal_cmp(pA, pB); + if( rc<0 ) rc = -1; + else if( rc>0 ) rc = +1; + sqlite3_result_int(context, rc); +cmp_done: + decimal_free(pA); + decimal_free(pB); +} + +/* +** Remove leading zeros from the Decimal +*/ +static void decimal_normalize(Decimal *p){ + int i; + int nSig; + if( p==0 ) return; + nSig = p->nDigit - p->nFrac; + for(i=0; ia[i]==0; i++){} + if( i ){ + memmove(p->a, p->a+i, p->nDigit - i); + p->nDigit -= i; + } +} + +/* +** Expand the Decimal so that it has a least nDigit digits and nFrac +** digits to the right of the decimal point. +*/ +static void decimal_expand(Decimal *p, int nDigit, int nFrac){ + int nAddSig; + int nAddFrac; + if( p==0 ) return; + nAddFrac = nFrac - p->nFrac; + nAddSig = (nDigit - p->nDigit) - nAddFrac; + if( nAddFrac==0 && nAddSig==0 ) return; + p->a = sqlite3_realloc64(p->a, nDigit+1); + if( p->a==0 ){ + p->oom = 1; + return; + } + if( nAddSig ){ + memmove(p->a+nAddSig, p->a, p->nDigit); + memset(p->a, 0, nAddSig); + p->nDigit += nAddSig; + } + if( nAddFrac ){ + memset(p->a+p->nDigit, 0, nAddFrac); + p->nDigit += nAddFrac; + p->nFrac += nAddFrac; + } +} + +/* +** Add the value pB into pA. +** +** pB might become denormalized by this routine. +*/ +static void decimal_add(Decimal *pA, Decimal *pB){ + int nSig, nFrac, nDigit; + int i, rc; + if( pA==0 ){ + return; + } + if( pA->oom || pB==0 || pB->oom ){ + pA->oom = 1; + return; + } + if( pA->isNull || pB->isNull ){ + pA->isNull = 1; + return; + } + nSig = pA->nDigit - pA->nFrac; + if( nSignDigit-pB->nFrac ) nSig = pB->nDigit - pB->nFrac; + nFrac = pA->nFrac; + if( nFracnFrac ) nFrac = pB->nFrac; + nDigit = nSig + nFrac + 1; + decimal_expand(pA, nDigit, nFrac); + decimal_expand(pB, nDigit, nFrac); + if( pA->oom || pB->oom ){ + pA->oom = 1; + }else{ + if( pA->sign==pB->sign ){ + int carry = 0; + for(i=nDigit-1; i>=0; i--){ + int x = pA->a[i] + pB->a[i] + carry; + if( x>=10 ){ + carry = 1; + pA->a[i] = x - 10; + }else{ + carry = 0; + pA->a[i] = x; + } + } + }else{ + signed char *aA, *aB; + int borrow = 0; + rc = memcmp(pA->a, pB->a, nDigit); + if( rc<0 ){ + aA = pB->a; + aB = pA->a; + pA->sign = !pA->sign; + }else{ + aA = pA->a; + aB = pB->a; + } + for(i=nDigit-1; i>=0; i--){ + int x = aA[i] - aB[i] - borrow; + if( x<0 ){ + pA->a[i] = x+10; + borrow = 1; + }else{ + pA->a[i] = x; + borrow = 0; + } + } + } + } + decimal_normalize(pA); +} + +/* +** Compare text in decimal order. +*/ +static int decimalCollFunc( + void *notUsed, + int nKey1, const void *pKey1, + int nKey2, const void *pKey2 +){ + const unsigned char *zA = (const unsigned char*)pKey1; + const unsigned char *zB = (const unsigned char*)pKey2; + Decimal *pA = decimal_new(0, 0, nKey1, zA); + Decimal *pB = decimal_new(0, 0, nKey2, zB); + int rc; + if( pA==0 || pB==0 ){ + rc = 0; + }else{ + rc = decimal_cmp(pA, pB); + } + decimal_free(pA); + decimal_free(pB); + return rc; +} + + +/* +** SQL Function: decimal_add(X, Y) +** decimal_sub(X, Y) +** +** Return the sum or difference of X and Y. +*/ +static void decimalAddFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *pA = decimal_new(context, argv[0], 0, 0); + Decimal *pB = decimal_new(context, argv[1], 0, 0); + decimal_add(pA, pB); + decimal_result(context, pA); + decimal_free(pA); + decimal_free(pB); +} +static void decimalSubFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *pA = decimal_new(context, argv[0], 0, 0); + Decimal *pB = decimal_new(context, argv[1], 0, 0); + if( pB==0 ) return; + pB->sign = !pB->sign; + decimal_add(pA, pB); + decimal_result(context, pA); + decimal_free(pA); + decimal_free(pB); +} + +/* Aggregate funcion: decimal_sum(X) +** +** Works like sum() except that it uses decimal arithmetic for unlimited +** precision. +*/ +static void decimalSumStep( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *p; + Decimal *pArg; + p = sqlite3_aggregate_context(context, sizeof(*p)); + if( p==0 ) return; + if( !p->isInit ){ + p->isInit = 1; + p->a = sqlite3_malloc(2); + if( p->a==0 ){ + p->oom = 1; + }else{ + p->a[0] = 0; + } + p->nDigit = 1; + p->nFrac = 0; + } + if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; + pArg = decimal_new(context, argv[0], 0, 0); + decimal_add(p, pArg); + decimal_free(pArg); +} +static void decimalSumInverse( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *p; + Decimal *pArg; + p = sqlite3_aggregate_context(context, sizeof(*p)); + if( p==0 ) return; + if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; + pArg = decimal_new(context, argv[0], 0, 0); + if( pArg ) pArg->sign = !pArg->sign; + decimal_add(p, pArg); + decimal_free(pArg); +} +static void decimalSumValue(sqlite3_context *context){ + Decimal *p = sqlite3_aggregate_context(context, 0); + if( p==0 ) return; + decimal_normalize(p); + decimal_result(context, p); +} +static void decimalSumFinalize(sqlite3_context *context){ + Decimal *p = sqlite3_aggregate_context(context, 0); + if( p==0 ) return; + decimal_normalize(p); + decimal_result(context, p); + decimal_clear(p); +} + + +#ifdef _WIN32 +__declspec(dllexport) +#endif +int sqlite3_decimal_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + SQLITE_EXTENSION_INIT2(pApi); + static const struct { + const char *zFuncName; + int nArg; + void (*xFunc)(sqlite3_context*,int,sqlite3_value**); + } aFunc[] = { + { "decimal", 1, decimalFunc }, + { "decimal_cmp", 2, decimalCmpFunc }, + { "decimal_add", 2, decimalAddFunc }, + { "decimal_sub", 2, decimalSubFunc }, + }; + int i; + (void)pzErrMsg; /* Unused parameter */ + + for(i=0; idb, 0, 0); sqlite3_completion_init(p->db, 0, 0); sqlite3_uint_init(p->db, 0, 0); + sqlite3_decimal_init(p->db, 0, 0); #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) sqlite3_dbdata_init(p->db, 0, 0); #endif diff --git a/src/test1.c b/src/test1.c index 8c8853737e..f94adf2d60 100644 --- a/src/test1.c +++ b/src/test1.c @@ -7257,6 +7257,7 @@ static int SQLITE_TCLAPI tclLoadStaticExtensionCmd( extern int sqlite3_eval_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_explain_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_fileio_init(sqlite3*,char**,const sqlite3_api_routines*); + extern int sqlite3_decimal_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_fuzzer_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_ieee_init(sqlite3*,char**,const sqlite3_api_routines*); extern int sqlite3_nextchar_init(sqlite3*,char**,const sqlite3_api_routines*); @@ -7282,6 +7283,7 @@ static int SQLITE_TCLAPI tclLoadStaticExtensionCmd( { "carray", sqlite3_carray_init }, { "closure", sqlite3_closure_init }, { "csv", sqlite3_csv_init }, + { "decimal", sqlite3_decimal_init }, { "eval", sqlite3_eval_init }, { "explain", sqlite3_explain_init }, { "fileio", sqlite3_fileio_init }, diff --git a/test/decimal.test b/test/decimal.test new file mode 100644 index 0000000000..1097d14db6 --- /dev/null +++ b/test/decimal.test @@ -0,0 +1,118 @@ +# 2017 December 9 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix decimal + +if {[catch {load_static_extension db decimal} error]} { + puts "Skipping zipfile tests, hit load error: $error" + finish_test; return +} + +do_execsql_test 1000 { + SELECT decimal(1); +} {1} +do_execsql_test 1010 { + SELECT decimal(1.0); +} {1.0} +do_execsql_test 1020 { + SELECT decimal(0001.0); +} {1.0} +do_execsql_test 1030 { + SELECT decimal(+0001.0); +} {1.0} +do_execsql_test 1040 { + SELECT decimal(-0001.0); +} {-1.0} +do_execsql_test 1050 { + SELECT decimal(1.0e72); +} {1000000000000000000000000000000000000000000000000000000000000000000000000} +# 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123 +do_execsql_test 1060 { + SELECT decimal(1.0e-72); +} {0.0000000000000000000000000000000000000000000000000000000000000000000000010} +# 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123 +do_execsql_test 1070 { + SELECT decimal(-123e-4); +} {-0.0123} +do_execsql_test 1080 { + SELECT decimal(+123e+4); +} {1230000.0} + + +do_execsql_test 2000 { + CREATE TABLE t1(seq INTEGER PRIMARY KEY, val TEXT); + INSERT INTO t1 VALUES + (1, '-9999e99'), + (2, '-9998.000e+99'), + (3, '-9999.0'), + (4, '-1'), + (5, '-9999e-20'), + (6, '0'), + (7, '1e-30'), + (8, '1e-29'), + (9, '1'), + (10,'1.00000000000000001'), + (11,'+1.00001'), + (12,'99e+99'); + SELECT *, '|' + FROM t1 AS a, t1 AS b + WHERE a.seq=0; +} {} +do_execsql_test 2010 { + SELECT *, '|' + FROM t1 AS a, t1 AS b + WHERE a.seq<>b.seq + AND decimal_cmp(a.val,b.val)==0; +} {} +do_execsql_test 2020 { + SELECT *, '|' + FROM t1 AS a, t1 AS b + WHERE a.seq>b.seq + AND decimal_cmp(a.val,b.val)<=0; +} {} +do_execsql_test 2030 { + SELECT seq FROM t1 ORDER BY val COLLATE decimal; +} {1 2 3 4 5 6 7 8 9 10 11 12} +do_execsql_test 2040 { + SELECT seq FROM t1 ORDER BY val COLLATE decimal DESC; +} {12 11 10 9 8 7 6 5 4 3 2 1} + +do_execsql_test 3000 { + CREATE TABLE t3(seq INTEGER PRIMARY KEY, val TEXT); + WITH RECURSIVE c(x) AS (VALUES(1) UNION SELECT x+1 FROM c WHERE x<10) + INSERT INTO t3(seq, val) SELECT x, x FROM c; + WITH RECURSIVE c(x) AS (VALUES(1) UNION SELECT x+1 FROM c WHERE x<5) + INSERT INTO t3(seq, val) SELECT x+10, x*1000 FROM c; + SELECT decimal(val) FROM t3 ORDER BY seq; +} {1 2 3 4 5 6 7 8 9 10 1000 2000 3000 4000 5000} +do_execsql_test 3020 { + SELECT decimal_add(val,'0.5') FROM t3 WHERE seq>5 ORDER BY seq +} {6.5 7.5 8.5 9.5 10.5 1000.5 2000.5 3000.5 4000.5 5000.5} +do_execsql_test 3030 { + SELECT decimal_add(val,'-10') FROM t3 ORDER BY seq; +} {-9 -8 -7 -6 -5 -4 -3 -2 -1 0 990 1990 2990 3990 4990} + +do_execsql_test 4000 { + SELECT decimal_sum(val) FROM t3; +} {15055} +do_execsql_test 4010 { + SELECT decimal_sum(decimal_add(val,val||'e+10')) FROM t3; +} {150550000015055} +do_execsql_test 4010 { + SELECT decimal_sum(decimal_add(val||'e+20',decimal_add(val,val||'e-20'))) + FROM t3; +} {1505500000000000000015055.00000000000000015055} + +finish_test From 427af8dc3d52ea18ddeef7ff35d4a022c0522cca Mon Sep 17 00:00:00 2001 From: drh Date: Mon, 22 Jun 2020 21:25:37 +0000 Subject: [PATCH 2/4] Add the decimal_mul() function. FossilOrigin-Name: 72eee04b67268ad38fd51ff32849f08c0a54cf1b481d5ecb11d77cc9c729ee03 --- ext/misc/decimal.c | 61 ++++++++++++++++++++++++++++++++++++++++++++-- manifest | 17 ++++++------- manifest.uuid | 2 +- test/decimal.test | 10 ++++++++ 4 files changed, 77 insertions(+), 13 deletions(-) diff --git a/ext/misc/decimal.c b/ext/misc/decimal.c index 078e91dc27..341b3c046d 100644 --- a/ext/misc/decimal.c +++ b/ext/misc/decimal.c @@ -298,7 +298,8 @@ cmp_done: } /* -** Remove leading zeros from the Decimal +** Remove leading zeros from before the decimal and +** trailing zeros after the decimal. */ static void decimal_normalize(Decimal *p){ int i; @@ -310,6 +311,10 @@ static void decimal_normalize(Decimal *p){ memmove(p->a, p->a+i, p->nDigit - i); p->nDigit -= i; } + while( p->nFrac && p->a[p->nDigit-1]==0 ){ + p->nFrac--; + p->nDigit--; + } } /* @@ -523,7 +528,58 @@ static void decimalSumFinalize(sqlite3_context *context){ decimal_result(context, p); decimal_clear(p); } - + +/* +** SQL Function: decimal_mul(X, Y) +** +** Return the product of X and Y. +*/ +static void decimalMulFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *pA = decimal_new(context, argv[0], 0, 0); + Decimal *pB = decimal_new(context, argv[1], 0, 0); + signed char *acc = 0; + int i, j, k; + if( pA==0 || pA->oom || pA->isNull + || pB==0 || pB->oom || pB->isNull + ){ + goto mul_end; + } + acc = sqlite3_malloc64( pA->nDigit + pB->nDigit + 2 ); + if( acc==0 ){ + sqlite3_result_error_nomem(context); + goto mul_end; + } + memset(acc, 0, pA->nDigit + pB->nDigit + 2); + for(i=pA->nDigit-1; i>=0; i--){ + signed char f = pA->a[i]; + int carry = 0, x; + for(j=pB->nDigit-1, k=i+j+3; j>=0; j--, k--){ + int x = acc[k] + f*pB->a[j] + carry; + acc[k] = x%10; + carry = x/10; + } + x = acc[k] + carry; + acc[k] = x%10; + acc[k-1] += x/10; + } + sqlite3_free(pA->a); + pA->a = acc; + acc = 0; + pA->nDigit += pB->nDigit + 2; + pA->nFrac += pB->nFrac; + pA->sign ^= pB->sign; + decimal_normalize(pA); + decimal_result(context, pA); + +mul_end: + sqlite3_free(acc); + decimal_free(pA); + decimal_free(pB); +} #ifdef _WIN32 __declspec(dllexport) @@ -544,6 +600,7 @@ int sqlite3_decimal_init( { "decimal_cmp", 2, decimalCmpFunc }, { "decimal_add", 2, decimalAddFunc }, { "decimal_sub", 2, decimalSubFunc }, + { "decimal_mul", 2, decimalMulFunc }, }; int i; (void)pzErrMsg; /* Unused parameter */ diff --git a/manifest b/manifest index 94d1d31c6c..4c38728caa 100644 --- a/manifest +++ b/manifest @@ -1,20 +1,17 @@ B 7a876209a678a34c198b54ceef9e3c041f128a14dc73357f6a57cadadaa6cf7b -C An\sextension\sfor\sdoing\sdecimal\sarithmetic\son\sstrings. -D 2020-06-22T19:12:23.405 +C Add\sthe\sdecimal_mul()\sfunction. +D 2020-06-22T21:25:37.690 F Makefile.in 014ad669b4a5809752939c2dea83722992711b464aa56adebca80c5cc98cfdc2 F Makefile.msc 08c8bbedfa51e21b57153370edbfee86d35bbefb53e3773a3e086fe654facccb F autoconf/Makefile.am a8d1d24affe52ebf8d7ddcf91aa973fa0316618ab95bb68c87cabf8faf527dc8 -F ext/misc/decimal.c ba7054d9719ceb034012e1744f6e9c1ca5fe2a00a2b83e34faf0c08a43204a64 +F ext/misc/decimal.c 334d8d34c124a8820d814dadbe1ac3daec63f504d5b322eb63671c317d3c6a3e F main.mk c99e452dd4edd0bea28547259421bd6cf24d41d928d24720737efc548958ca39 F src/build.c ba1bbe563a3dc02d5fed20537603181e5289c13ea30ae5e775f552e7557adbfa F src/shell.c.in 7fe12fb6452de32f92bb26ee50524ec31c9d08a1c46376ca00a4ecd27472a14d F src/test1.c fe56c4bcaa2685ca9aa25d817a0ee9345e189aff4a5a71a3d8ba946c7776feb8 -F test/decimal.test 5c486a6baa87fe0cd1c119ffa190bcc70a23e2dd6617baa201eab9e0b0ef2efd +F test/decimal.test c4af0264a0f30e896f617b489d69039932fe5127b02ac221d96f8af31db13826 F tool/mkautoconfamal.sh f62353eb6c06ab264da027fd4507d09914433dbdcab9cb011cdc18016f1ab3b8 -P 067291143a63db924ead4810defb4bc6f195557412f5d1c22299f30d2d9f2a79 -R 4df2ada42f27e695d7e98ee101d73989 -T *branch * decimal -T *sym-decimal * -T -sym-trunk * +P 4c3b85607feb53462ccc8b89bea699fdb132c402eae597afa33cc40a85c32329 +R d79a1d72394e5a5f6c93e9466f2c9aa1 U drh -Z ac0625ef3228b5eb95c4972028cf8994 +Z 3092a9d89a34e083e375231f8e857b54 diff --git a/manifest.uuid b/manifest.uuid index dd813196a9..997afdbdeb 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -4c3b85607feb53462ccc8b89bea699fdb132c402eae597afa33cc40a85c32329 \ No newline at end of file +72eee04b67268ad38fd51ff32849f08c0a54cf1b481d5ecb11d77cc9c729ee03 \ No newline at end of file diff --git a/test/decimal.test b/test/decimal.test index 1097d14db6..726f470bcc 100644 --- a/test/decimal.test +++ b/test/decimal.test @@ -115,4 +115,14 @@ do_execsql_test 4010 { FROM t3; } {1505500000000000000015055.00000000000000015055} +do_execsql_test 5000 { + WITH RECURSIVE c(x,y,z) AS ( + VALUES(0,'1','1') + UNION ALL + SELECT x+1, decimal_mul(y,'2'), decimal_mul(z,'0.5') + FROM c WHERE x<32 + ) + SELECT count(*) FROM c WHERE decimal_mul(y,z)='1'; +} {33} + finish_test From 6b647186186cc01237d600ace86602d72d4f58d0 Mon Sep 17 00:00:00 2001 From: drh Date: Tue, 23 Jun 2020 14:44:57 +0000 Subject: [PATCH 3/4] Avoid unnecessary normalization in decimal_sum(). Trim excess trailing zeros off the result of decimal_mul(). FossilOrigin-Name: 0294ce071c863eb517e97beff31c3d95a4370e979a969415162302a90d3fda0e --- ext/misc/decimal.c | 47 +++++++++++++++++++++------------------------- manifest | 14 +++++++------- manifest.uuid | 2 +- test/decimal.test | 14 ++++++++++++++ 4 files changed, 43 insertions(+), 34 deletions(-) diff --git a/ext/misc/decimal.c b/ext/misc/decimal.c index 341b3c046d..43c5c2ef0d 100644 --- a/ext/misc/decimal.c +++ b/ext/misc/decimal.c @@ -204,6 +204,10 @@ static void decimal_result(sqlite3_context *pCtx, Decimal *p){ z[i++] = '0'; } j = 0; + while( n>1 && p->a[j]==0 ){ + j++; + n--; + } while( n>0 ){ z[i++] = p->a[j] + '0'; j++; @@ -297,26 +301,6 @@ cmp_done: decimal_free(pB); } -/* -** Remove leading zeros from before the decimal and -** trailing zeros after the decimal. -*/ -static void decimal_normalize(Decimal *p){ - int i; - int nSig; - if( p==0 ) return; - nSig = p->nDigit - p->nFrac; - for(i=0; ia[i]==0; i++){} - if( i ){ - memmove(p->a, p->a+i, p->nDigit - i); - p->nDigit -= i; - } - while( p->nFrac && p->a[p->nDigit-1]==0 ){ - p->nFrac--; - p->nDigit--; - } -} - /* ** Expand the Decimal so that it has a least nDigit digits and nFrac ** digits to the right of the decimal point. @@ -348,7 +332,7 @@ static void decimal_expand(Decimal *p, int nDigit, int nFrac){ /* ** Add the value pB into pA. ** -** pB might become denormalized by this routine. +** Both pA and pB might become denormalized by this routine. */ static void decimal_add(Decimal *pA, Decimal *pB){ int nSig, nFrac, nDigit; @@ -365,7 +349,10 @@ static void decimal_add(Decimal *pA, Decimal *pB){ return; } nSig = pA->nDigit - pA->nFrac; - if( nSignDigit-pB->nFrac ) nSig = pB->nDigit - pB->nFrac; + if( nSig && pA->a[0]==0 ) nSig--; + if( nSignDigit-pB->nFrac ){ + nSig = pB->nDigit - pB->nFrac; + } nFrac = pA->nFrac; if( nFracnFrac ) nFrac = pB->nFrac; nDigit = nSig + nFrac + 1; @@ -410,7 +397,6 @@ static void decimal_add(Decimal *pA, Decimal *pB){ } } } - decimal_normalize(pA); } /* @@ -518,13 +504,11 @@ static void decimalSumInverse( static void decimalSumValue(sqlite3_context *context){ Decimal *p = sqlite3_aggregate_context(context, 0); if( p==0 ) return; - decimal_normalize(p); decimal_result(context, p); } static void decimalSumFinalize(sqlite3_context *context){ Decimal *p = sqlite3_aggregate_context(context, 0); if( p==0 ) return; - decimal_normalize(p); decimal_result(context, p); decimal_clear(p); } @@ -533,6 +517,11 @@ static void decimalSumFinalize(sqlite3_context *context){ ** SQL Function: decimal_mul(X, Y) ** ** Return the product of X and Y. +** +** All significant digits after the decimal point are retained. +** Trailing zeros after the decimal point are omitted as long as +** the number of digits after the decimal point is no less than +** either the number of digits in either input. */ static void decimalMulFunc( sqlite3_context *context, @@ -543,6 +532,7 @@ static void decimalMulFunc( Decimal *pB = decimal_new(context, argv[1], 0, 0); signed char *acc = 0; int i, j, k; + int minFrac; if( pA==0 || pA->oom || pA->isNull || pB==0 || pB->oom || pB->isNull ){ @@ -554,6 +544,8 @@ static void decimalMulFunc( goto mul_end; } memset(acc, 0, pA->nDigit + pB->nDigit + 2); + minFrac = pA->nFrac; + if( pB->nFracnFrac; for(i=pA->nDigit-1; i>=0; i--){ signed char f = pA->a[i]; int carry = 0, x; @@ -572,7 +564,10 @@ static void decimalMulFunc( pA->nDigit += pB->nDigit + 2; pA->nFrac += pB->nFrac; pA->sign ^= pB->sign; - decimal_normalize(pA); + while( pA->nFrac>minFrac && pA->a[pA->nDigit-1]==0 ){ + pA->nFrac--; + pA->nDigit--; + } decimal_result(context, pA); mul_end: diff --git a/manifest b/manifest index 4c38728caa..5cbe47f9cc 100644 --- a/manifest +++ b/manifest @@ -1,17 +1,17 @@ B 7a876209a678a34c198b54ceef9e3c041f128a14dc73357f6a57cadadaa6cf7b -C Add\sthe\sdecimal_mul()\sfunction. -D 2020-06-22T21:25:37.690 +C Avoid\sunnecessary\snormalization\sin\sdecimal_sum().\s\sTrim\sexcess\strailing\nzeros\soff\sthe\sresult\sof\sdecimal_mul(). +D 2020-06-23T14:44:57.292 F Makefile.in 014ad669b4a5809752939c2dea83722992711b464aa56adebca80c5cc98cfdc2 F Makefile.msc 08c8bbedfa51e21b57153370edbfee86d35bbefb53e3773a3e086fe654facccb F autoconf/Makefile.am a8d1d24affe52ebf8d7ddcf91aa973fa0316618ab95bb68c87cabf8faf527dc8 -F ext/misc/decimal.c 334d8d34c124a8820d814dadbe1ac3daec63f504d5b322eb63671c317d3c6a3e +F ext/misc/decimal.c 7ddba759b25b433231d1d9ca7cfba947992856059e2eefc5664160d4163c71d2 F main.mk c99e452dd4edd0bea28547259421bd6cf24d41d928d24720737efc548958ca39 F src/build.c ba1bbe563a3dc02d5fed20537603181e5289c13ea30ae5e775f552e7557adbfa F src/shell.c.in 7fe12fb6452de32f92bb26ee50524ec31c9d08a1c46376ca00a4ecd27472a14d F src/test1.c fe56c4bcaa2685ca9aa25d817a0ee9345e189aff4a5a71a3d8ba946c7776feb8 -F test/decimal.test c4af0264a0f30e896f617b489d69039932fe5127b02ac221d96f8af31db13826 +F test/decimal.test 12739a01bdba4c4d79f95b323e6b67b9fad1ab6ffb56116bd2b9c81a5b19e1d9 F tool/mkautoconfamal.sh f62353eb6c06ab264da027fd4507d09914433dbdcab9cb011cdc18016f1ab3b8 -P 4c3b85607feb53462ccc8b89bea699fdb132c402eae597afa33cc40a85c32329 -R d79a1d72394e5a5f6c93e9466f2c9aa1 +P 72eee04b67268ad38fd51ff32849f08c0a54cf1b481d5ecb11d77cc9c729ee03 +R 91beb964e18be89b7d24fd58df29ec37 U drh -Z 3092a9d89a34e083e375231f8e857b54 +Z 2211c9af276c1b9a10695b6c4b82f27a diff --git a/manifest.uuid b/manifest.uuid index 997afdbdeb..f937a3c5a4 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -72eee04b67268ad38fd51ff32849f08c0a54cf1b481d5ecb11d77cc9c729ee03 \ No newline at end of file +0294ce071c863eb517e97beff31c3d95a4370e979a969415162302a90d3fda0e \ No newline at end of file diff --git a/test/decimal.test b/test/decimal.test index 726f470bcc..6fb15340f8 100644 --- a/test/decimal.test +++ b/test/decimal.test @@ -125,4 +125,18 @@ do_execsql_test 5000 { SELECT count(*) FROM c WHERE decimal_mul(y,z)='1'; } {33} +do_execsql_test 5100 { + SELECT decimal_mul('1234.00','2.00'); +} {2468.00} +do_execsql_test 5101 { + SELECT decimal_mul('1234.00','2.0000'); +} {2468.00} +do_execsql_test 5102 { + SELECT decimal_mul('1234.0000','2.000'); +} {2468.000} +do_execsql_test 5103 { + SELECT decimal_mul('1234.0000','2'); +} {2468} + + finish_test From e78c027b6440ed9f68ed32cf66c86f11adb3997a Mon Sep 17 00:00:00 2001 From: drh Date: Tue, 23 Jun 2020 15:29:22 +0000 Subject: [PATCH 4/4] Fix a harmless compiler warning. FossilOrigin-Name: e1416c8b0628afa062d8cff40d0cd3576dc85460e55b21a271f88fcb608b9f59 --- ext/misc/decimal.c | 2 +- manifest | 12 ++++++------ manifest.uuid | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ext/misc/decimal.c b/ext/misc/decimal.c index 43c5c2ef0d..e2c9d1ba87 100644 --- a/ext/misc/decimal.c +++ b/ext/misc/decimal.c @@ -550,7 +550,7 @@ static void decimalMulFunc( signed char f = pA->a[i]; int carry = 0, x; for(j=pB->nDigit-1, k=i+j+3; j>=0; j--, k--){ - int x = acc[k] + f*pB->a[j] + carry; + x = acc[k] + f*pB->a[j] + carry; acc[k] = x%10; carry = x/10; } diff --git a/manifest b/manifest index 5cbe47f9cc..6dd3fe4830 100644 --- a/manifest +++ b/manifest @@ -1,17 +1,17 @@ B 7a876209a678a34c198b54ceef9e3c041f128a14dc73357f6a57cadadaa6cf7b -C Avoid\sunnecessary\snormalization\sin\sdecimal_sum().\s\sTrim\sexcess\strailing\nzeros\soff\sthe\sresult\sof\sdecimal_mul(). -D 2020-06-23T14:44:57.292 +C Fix\sa\sharmless\scompiler\swarning. +D 2020-06-23T15:29:22.981 F Makefile.in 014ad669b4a5809752939c2dea83722992711b464aa56adebca80c5cc98cfdc2 F Makefile.msc 08c8bbedfa51e21b57153370edbfee86d35bbefb53e3773a3e086fe654facccb F autoconf/Makefile.am a8d1d24affe52ebf8d7ddcf91aa973fa0316618ab95bb68c87cabf8faf527dc8 -F ext/misc/decimal.c 7ddba759b25b433231d1d9ca7cfba947992856059e2eefc5664160d4163c71d2 +F ext/misc/decimal.c c1897f624893d1c12e3c879d97ca7d1c4a36cae10d32afe632779de78c4aaa4f F main.mk c99e452dd4edd0bea28547259421bd6cf24d41d928d24720737efc548958ca39 F src/build.c ba1bbe563a3dc02d5fed20537603181e5289c13ea30ae5e775f552e7557adbfa F src/shell.c.in 7fe12fb6452de32f92bb26ee50524ec31c9d08a1c46376ca00a4ecd27472a14d F src/test1.c fe56c4bcaa2685ca9aa25d817a0ee9345e189aff4a5a71a3d8ba946c7776feb8 F test/decimal.test 12739a01bdba4c4d79f95b323e6b67b9fad1ab6ffb56116bd2b9c81a5b19e1d9 F tool/mkautoconfamal.sh f62353eb6c06ab264da027fd4507d09914433dbdcab9cb011cdc18016f1ab3b8 -P 72eee04b67268ad38fd51ff32849f08c0a54cf1b481d5ecb11d77cc9c729ee03 -R 91beb964e18be89b7d24fd58df29ec37 +P 0294ce071c863eb517e97beff31c3d95a4370e979a969415162302a90d3fda0e +R 22a8b6c5a17661f94a4e4c4e5bc40125 U drh -Z 2211c9af276c1b9a10695b6c4b82f27a +Z 4336a95e2cf12209e8acd933005b2062 diff --git a/manifest.uuid b/manifest.uuid index f937a3c5a4..87e8e2340e 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -0294ce071c863eb517e97beff31c3d95a4370e979a969415162302a90d3fda0e \ No newline at end of file +e1416c8b0628afa062d8cff40d0cd3576dc85460e55b21a271f88fcb608b9f59 \ No newline at end of file