]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Enhancements to the DECIMAL extension:
authordrh <>
Thu, 29 Jun 2023 20:28:03 +0000 (20:28 +0000)
committerdrh <>
Thu, 29 Jun 2023 20:28:03 +0000 (20:28 +0000)
(1) If the argument to decimal(X) is a floating point value (or an 8-byte blob),
the floating point value is expanded into its exact decimal representation.
(2) Function decimal_sci(X) works the same except it returns the result in
scientific notation.
(3) New function decimal_pow2(N) returns the full decimal expansion of the N-th
integer power of 2.

FossilOrigin-Name: 8baf8c10aecb261751f2b154356ab224b79d07230929ec9f123791278e601bba

ext/misc/decimal.c
manifest
manifest.uuid
test/decimal.test

index 865f3ce2416e5cf18a3d3b5e75f7515f002b46f4..6c080c200fc707d2859df290efc3abea249b69d9 100644 (file)
@@ -291,22 +291,6 @@ static void decimal_result_sci(sqlite3_context *pCtx, Decimal *p){
   sqlite3_result_text(pCtx, z, -1, 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);
-  UNUSED_PARAMETER(argc);
-  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.
@@ -399,7 +383,7 @@ static void decimal_expand(Decimal *p, int nDigit, int nFrac){
 }
 
 /*
-** Add the value pB into pA.
+** Add the value pB into pA.   A := A + B.
 **
 ** Both pA and pB might become denormalized by this routine.
 */
@@ -468,6 +452,200 @@ static void decimal_add(Decimal *pA, Decimal *pB){
   }
 }
 
+/*
+** Multiply A by B.   A := A * B
+**
+** 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 decimalMul(Decimal *pA, Decimal *pB){
+  signed char *acc = 0;
+  int i, j, k;
+  int minFrac;
+
+  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 ){
+    pA->oom = 1;
+    goto mul_end;
+  }
+  memset(acc, 0, pA->nDigit + pB->nDigit + 2);
+  minFrac = pA->nFrac;
+  if( pB->nFrac<minFrac ) minFrac = pB->nFrac;
+  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--){
+      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;
+  while( pA->nFrac>minFrac && pA->a[pA->nDigit-1]==0 ){
+    pA->nFrac--;
+    pA->nDigit--;
+  }
+
+mul_end:
+  sqlite3_free(acc);
+}
+
+/*
+** Create a new Decimal object that contains an integer power of 2.
+*/
+static Decimal *decimalPow2(int N){
+  Decimal *pA = 0;      /* The result to be returned */
+  Decimal *pX = 0;      /* Multiplier */
+  if( N<-20000 || N>20000 ) goto pow2_fault;
+  pA = decimal_new(0, 0, 3, (unsigned char*)"1.0");
+  if( pA==0 || pA->oom ) goto pow2_fault;
+  if( N==0 ) return pA;
+  if( N>0 ){
+    pX = decimal_new(0, 0, 3, (unsigned char*)"2.0");
+  }else{
+    N = -N;
+    pX = decimal_new(0, 0, 3, (unsigned char*)"0.5");
+  }
+  if( pX==0 || pX->oom ) goto pow2_fault;
+  while( 1 /* Exit by break */ ){
+    if( N & 1 ){
+      decimalMul(pA, pX);
+      if( pA->oom ) goto pow2_fault;
+    }
+    N >>= 1;
+    if( N==0 ) break;
+    decimalMul(pX, pX);
+  }
+  decimal_free(pX);
+  return pA;
+
+pow2_fault:
+  decimal_free(pA);
+  decimal_free(pX);
+  return 0;
+}
+
+/*
+** Use an IEEE754 binary64 ("double") to generate a new Decimal object.
+*/
+static Decimal *decimalFromDouble(double r){
+  sqlite3_int64 m, a;
+  int e;
+  int isNeg;
+  Decimal *pA;
+  Decimal *pX;
+  char zNum[100];
+  if( r<0.0 ){
+    isNeg = 1;
+    r = -r;
+  }else{
+    isNeg = 0;
+  }
+  memcpy(&a,&r,sizeof(a));
+  if( a==0 ){
+    e = 0;
+    m = 0;
+  }else{
+    e = a>>52;
+    m = a & ((((sqlite3_int64)1)<<52)-1);
+    if( e==0 ){
+      m <<= 1;
+    }else{
+      m |= ((sqlite3_int64)1)<<52;
+    }
+    while( e<1075 && m>0 && (m&1)==0 ){
+      m >>= 1;
+      e++;
+    }
+    if( isNeg ) m = -m;
+    e = e - 1075;
+    if( e>971 ){
+      return 0;  /* A NaN or an Infinity */
+    }
+  }
+
+  /* At this point m is the integer significand and e is the exponent */
+  sqlite3_snprintf(sizeof(zNum), zNum, "%lld", m);
+  pA = decimal_new(0, 0, (int)strlen(zNum), (unsigned char*)zNum);
+  pX = decimalPow2(e);
+  decimalMul(pA, pX);
+  decimal_free(pX);
+  return pA;
+}
+
+/*
+** SQL Function:   decimal(X)
+**
+** Convert input X into decimal and then back into text.
+**
+** If X is originally a float, then a full decoding of that floating
+** point value is done.  Or if X is an 8-byte blob, it is interpreted
+** as a float and similarly expanded.
+*/
+static void decimalFunc(
+  sqlite3_context *context,
+  int argc,
+  sqlite3_value **argv
+){
+  Decimal *p = 0;
+  UNUSED_PARAMETER(argc);
+  switch( sqlite3_value_type(argv[0]) ){
+    case SQLITE_TEXT:
+    case SQLITE_INTEGER: {
+      p = decimal_new(context, argv[0], 0, 0);
+      break;
+    }
+
+    case SQLITE_FLOAT: {
+      p = decimalFromDouble(sqlite3_value_double(argv[0]));
+      break;
+    }
+
+    case SQLITE_BLOB: {
+      const unsigned char *x;
+      unsigned int i;
+      sqlite3_uint64 v = 0;
+      double r;
+
+      if( sqlite3_value_bytes(argv[0])!=sizeof(r) ) break;
+      x = sqlite3_value_blob(argv[0]);
+      for(i=0; i<sizeof(r); i++){
+        v = (v<<8) | x[i];
+      }
+      memcpy(&r, &v, sizeof(r));
+      p = decimalFromDouble(r);
+      break;
+    }
+
+    case SQLITE_NULL: {
+      break;
+    }
+  }
+  if( p ){
+    if( sqlite3_user_data(context)!=0 ){
+      decimal_result_sci(context, p);
+    }else{
+      decimal_result(context, p);
+    }
+    decimal_free(p);
+  }
+}
+
 /*
 ** Compare text in decimal order.
 */
@@ -592,11 +770,6 @@ 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,
@@ -605,67 +778,39 @@ static void decimalMulFunc(
 ){
   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;
-  int minFrac;
   UNUSED_PARAMETER(argc);
   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);
+  decimalMul(pA, pB);
+  if( pA->oom ){
     goto mul_end;
   }
-  memset(acc, 0, pA->nDigit + pB->nDigit + 2);
-  minFrac = pA->nFrac;
-  if( pB->nFrac<minFrac ) minFrac = pB->nFrac;
-  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--){
-      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;
-  while( pA->nFrac>minFrac && pA->a[pA->nDigit-1]==0 ){
-    pA->nFrac--;
-    pA->nDigit--;
-  }
   decimal_result(context, pA);
 
 mul_end:
-  sqlite3_free(acc);
   decimal_free(pA);
   decimal_free(pB);
 }
 
 /*
-** SQL Function:   decimal_sci(X)
+** SQL Function:   decimal_pow2(N)
 **
-** Convert decimal number X into scientific notation ("+N.NNNe+NN").
+** Return the N-th power of 2.  N must be an integer.
 */
-static void decimalSciFunc(
+static void decimalPow2Func(
   sqlite3_context *context,
   int argc,
   sqlite3_value **argv
 ){
-  Decimal *pA = decimal_new(context, argv[0], 0, 0);
   UNUSED_PARAMETER(argc);
-  decimal_result_sci(context, pA);
-  decimal_free(pA);
+  if( sqlite3_value_type(argv[0])==SQLITE_INTEGER ){
+    Decimal *pA = decimalPow2(sqlite3_value_int(argv[0]));
+    decimal_result_sci(context, pA);
+    decimal_free(pA);
+  }
 }
 
 #ifdef _WIN32
@@ -680,14 +825,16 @@ int sqlite3_decimal_init(
   static const struct {
     const char *zFuncName;
     int nArg;
+    int iArg;
     void (*xFunc)(sqlite3_context*,int,sqlite3_value**);
   } aFunc[] = {
-    { "decimal",       1,   decimalFunc        },
-    { "decimal_cmp",   2,   decimalCmpFunc     },
-    { "decimal_add",   2,   decimalAddFunc     },
-    { "decimal_sub",   2,   decimalSubFunc     },
-    { "decimal_mul",   2,   decimalMulFunc     },
-    { "decimal_sci",   1,   decimalSciFunc     },
+    { "decimal",       1, 0,  decimalFunc        },
+    { "decimal_cmp",   2, 0,  decimalCmpFunc     },
+    { "decimal_add",   2, 0,  decimalAddFunc     },
+    { "decimal_sub",   2, 0,  decimalSubFunc     },
+    { "decimal_mul",   2, 0,  decimalMulFunc     },
+    { "decimal_sci",   1, 1,  decimalFunc        },
+    { "decimal_pow2",  1, 0,  decimalPow2Func    },
   };
   unsigned int i;
   (void)pzErrMsg;  /* Unused parameter */
@@ -697,7 +844,7 @@ int sqlite3_decimal_init(
   for(i=0; i<(int)(sizeof(aFunc)/sizeof(aFunc[0])) && rc==SQLITE_OK; i++){
     rc = sqlite3_create_function(db, aFunc[i].zFuncName, aFunc[i].nArg,
                    SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC,
-                   0, aFunc[i].xFunc, 0, 0);
+                   aFunc[i].iArg ? db : 0, aFunc[i].xFunc, 0, 0);
   }
   if( rc==SQLITE_OK ){
     rc = sqlite3_create_window_function(db, "decimal_sum", 1,
index 3d0377b14df94cd9e048a7c2b7f3a8fc1cda8233..84770747b1dfce9b6d5e111aab25454545058637 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Fix\sharmless\scompiler\swarnings\sabout\sunused\sfunction\sarguments.
-D 2023-06-29T17:48:32.337
+C Enhancements\sto\sthe\sDECIMAL\sextension:\n(1)\sIf\sthe\sargument\sto\sdecimal(X)\sis\sa\sfloating\spoint\svalue\s(or\san\s8-byte\sblob),\nthe\sfloating\spoint\svalue\sis\sexpanded\sinto\sits\sexact\sdecimal\srepresentation.\n(2)\sFunction\sdecimal_sci(X)\sworks\sthe\ssame\sexcept\sit\sreturns\sthe\sresult\sin\nscientific\snotation.\n(3)\sNew\sfunction\sdecimal_pow2(N)\sreturns\sthe\sfull\sdecimal\sexpansion\sof\sthe\sN-th\ninteger\spower\sof\s2.
+D 2023-06-29T20:28:03.266
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -287,7 +287,7 @@ F ext/misc/completion.c 6dafd7f4348eecc7be9e920d4b419d1fb2af75d938cd9c59a20cfe8b
 F ext/misc/compress.c 3354c77a7c8e86e07d849916000cdac451ed96500bfb5bd83b20eb61eee012c9
 F ext/misc/csv.c ca8d6dafc5469639de81937cb66ae2e6b358542aba94c4f791910d355a8e7f73
 F ext/misc/dbdump.c b8592f6f2da292c62991a13864a60d6c573c47a9cc58362131b9e6a64f823e01
-F ext/misc/decimal.c 24ccb63e9af6ed7de2e8e3b300061ad91169a44cc0c35dab92b7d2e1e7574f28
+F ext/misc/decimal.c 9899f6c3d2622f12a658530bdaff931ad1633d6e65d58e4a5092122dd188d97d
 F ext/misc/eval.c 04bc9aada78c888394204b4ed996ab834b99726fb59603b0ee3ed6e049755dc1
 F ext/misc/explain.c 0086fab288d4352ea638cf40ac382aad3b0dc5e845a1ea829a694c015fd970fe
 F ext/misc/fileio.c 4e7f7cd30de8df4820c552f14af3c9ca451c5ffe1f2e7bef34d598a12ebfb720
@@ -939,7 +939,7 @@ F test/dbpage.test fce29035c7566fd7835ec0f19422cb4b9c6944ce0e1b936ff8452443f92e8
 F test/dbpagefault.test d9111a62f3601d3efc6841ace3940181937342d245f92a1cca6cba8206d4f58a
 F test/dbstatus.test 4a4221a883025ffd39696b3d1b3910b928fb097d77e671351acb35f3aed42759
 F test/dbstatus2.test f5fe0afed3fa45e57cfa70d1147606c20d2ba23feac78e9a172f2fe8ab5b78ef
-F test/decimal.test fcf403fd5585f47342234e153c4a4338cd737b8e0884ac66fc484df47dbcf1a7
+F test/decimal.test 18e7b4cb12e8d5c60d768b686ba52af3e1ca3ced4f870231f0476666fd9fab7e
 F test/default.test 9687cfb16717e4b8238c191697c98be88c0b16e568dd5368cd9284154097ef50
 F test/delete.test 2686e1c98d552ef37d79ad55b17b93fe96fad9737786917ce3839767f734c48f
 F test/delete2.test 3a03f2cca1f9a67ec469915cb8babd6485db43fa
@@ -2041,8 +2041,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 41580ba452fdbc3f73da60d8030289d38614c4cab8d24140d7cc44a54b2da8d2
-R 479219669b5b9c2257f0511e6dc18154
+P 24927c1377314a10177da4a57191593440aa97fd0c5949fdf25a22df1d947600
+R 3c66bdef3f553aa91a9987834f7b8233
 U drh
-Z e7211a376de07c93dd5c0d56ad175411
+Z 21790c2ceaea39dee44b1391a0c9d87f
 # Remove this line to create a well-formed Fossil manifest.
index 5f3f75a4be9d19b9d7e87ef4cf38e7b4a1c2310d..b959f005c92ff36c1ab832317ea2c2e6c3824f01 100644 (file)
@@ -1 +1 @@
-24927c1377314a10177da4a57191593440aa97fd0c5949fdf25a22df1d947600
\ No newline at end of file
+8baf8c10aecb261751f2b154356ab224b79d07230929ec9f123791278e601bba
\ No newline at end of file
index a2e57997dda027c3b6d3f76b2147506e09eefedd..6ce5d642ddac39cc0522ca0b93940ec32bd685ba 100644 (file)
@@ -23,31 +23,34 @@ do_execsql_test 1000 {
   SELECT decimal(1);
 } {1}
 do_execsql_test 1010 {
-  SELECT decimal(1.0);
+  SELECT decimal('1.0');
 } {1.0}
 do_execsql_test 1020 {
-  SELECT decimal(0001.0);
+  SELECT decimal('0001.0');
 } {1.0}
 do_execsql_test 1030 {
-  SELECT decimal(+0001.0);
+  SELECT decimal('+0001.0');
 } {1.0}
 do_execsql_test 1040 {
-  SELECT decimal(-0001.0);
+  SELECT decimal('-0001.0');
 } {-1.0}
 do_execsql_test 1050 {
-  SELECT decimal(1.0e72);
+  SELECT decimal('1.0e72');
 } {1000000000000000000000000000000000000000000000000000000000000000000000000}
 #   123456789 123456789 123456789 123456789 123456789 123456789 123456789 123
 do_execsql_test 1060 {
-  SELECT decimal(1.0e-72);
+  SELECT decimal('1.0e-72');
 } {0.0000000000000000000000000000000000000000000000000000000000000000000000010}
 #    123456789 123456789 123456789 123456789 123456789 123456789 123456789 123
 do_execsql_test 1070 {
-  SELECT decimal(-123e-4);
+  SELECT decimal('-123e-4');
 } {-0.0123}
 do_execsql_test 1080 {
-  SELECT decimal(+123e+4);
-} {1230000.0}
+  SELECT decimal('+123e+4');
+} {1230000}
+do_execsql_test 1081 {
+  SELECT decimal_sci('+123e+4');
+} {+1.23e+06}
 
 
 do_execsql_test 2000 {
@@ -161,6 +164,10 @@ do_execsql_test 6010 {
 SELECT decimal_mul(ieee754_mantissa(c.n),pow2.v)
   FROM pow2, c WHERE pow2.x=ieee754_exponent(c.n);
 } {0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004940656458412465441765687928682213723650598026143247644255856825006755072702087518652998363616359923797965646954457177309266567103559397963987747960107818781263007131903114045278458171678489821036887186360569987307230500063874091535649843873124733972731696151400317153853980741262385655911710266585566867681870395603106249319452715914924553293054565444011274801297099995419319894090804165633245247571478690147267801593552386115501348035264934720193790268107107491703332226844753335720832431936092382893458368060106011506169809753078342277318329247904982524730776375927247874656084778203734469699533647017972677717585125660551199131504891101451037862738167250955837389733598993664809941164205702637090279242767544565229087538682506419718265533447265625}
+do_execsql_test 6011 {
+  WITH c(n) AS (SELECT ieee754_from_blob(x'0000000000000001'))
+SELECT decimal(c.n) FROM c;
+} {0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004940656458412465441765687928682213723650598026143247644255856825006755072702087518652998363616359923797965646954457177309266567103559397963987747960107818781263007131903114045278458171678489821036887186360569987307230500063874091535649843873124733972731696151400317153853980741262385655911710266585566867681870395603106249319452715914924553293054565444011274801297099995419319894090804165633245247571478690147267801593552386115501348035264934720193790268107107491703332226844753335720832431936092382893458368060106011506169809753078342277318329247904982524730776375927247874656084778203734469699533647017972677717585125660551199131504891101451037862738167250955837389733598993664809941164205702637090279242767544565229087538682506419718265533447265625}
 do_execsql_test 6020 {
   WITH c(n) AS (SELECT ieee754_from_blob(x'7fefffffffffffff'))
 SELECT decimal_mul(ieee754_mantissa(c.n),pow2.v)