]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Change the rounding behavior of float point to decimal conversions such that
authordrh <>
Mon, 10 Jun 2024 14:31:07 +0000 (14:31 +0000)
committerdrh <>
Mon, 10 Jun 2024 14:31:07 +0000 (14:31 +0000)
if the next digit is 4 but the value is within one epsilon of the next digit
being 5 and if the epsilon is small compared the number of digits to be
rendered, then go ahead and round up anyhow,
even though the correct behavior would be to round down.

FossilOrigin-Name: 4a790d3b28685f08bbb722057cd6a97aea08a2b2a6098562c6373fd3b5b7206c

manifest
manifest.uuid
src/util.c
test/round2.test [new file with mode: 0644]

index 074d16815b467799fa90e4291aa73487d811a5e9..8d40a46dd4405f679abdfab99a464266943d891a 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Improved\sheader\scomment\son\sthe\ssqlite3FpDecode()\simplementation.\nFor\sthe\sfpdecode()\sSQL\sfunction\s(available\sin\sdebug\sbuilds\sonly)\slimit\nthe\svalue\sof\sthe\sthird\sparameter\s(mxRound)\sto\sbe\spositive.
-D 2024-06-10T12:43:03.794
+C Change\sthe\srounding\sbehavior\sof\sfloat\spoint\sto\sdecimal\sconversions\ssuch\sthat\nif\sthe\snext\sdigit\sis\s4\sbut\sthe\svalue\sis\swithin\sone\sepsilon\sof\sthe\snext\sdigit\nbeing\s5\sand\sif\sthe\sepsilon\sis\ssmall\scompared\sthe\snumber\sof\sdigits\sto\sbe\nrendered,\sthen\sgo\sahead\sand\sround\sup\sanyhow,\neven\sthough\sthe\scorrect\sbehavior\swould\sbe\sto\sround\sdown.
+D 2024-06-10T14:31:07.017
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -823,7 +823,7 @@ F src/trigger.c 0858f75818ed1580332db274f1032bcc5effe567cb132df5c5be8b1d800ca97f
 F src/update.c 732404a04d1737ef14bb6ec6b84f74edf28b3c102a92ae46b4855438a710efe7
 F src/upsert.c 2e60567a0e9e8520c18671b30712a88dc73534474304af94f32bb5f3ef65ac65
 F src/utf.c f23165685a67b4caf8ec08fb274cb3f319103decfb2a980b7cfd55d18dfa855e
-F src/util.c 5d1a0134cf4240648d1c6bb5cc8efaca0ea2b5d5c840985aec7e947271f04375
+F src/util.c cedda44359c51d971557614216085c31d78ee7d23cc89400061c5433855aff64
 F src/vacuum.c 604fcdaebe76f3497c855afcbf91b8fa5046b32de3045bab89cc008d68e40104
 F src/vdbe.c b05777c3ff2ed7b9dfc347e7cdee18e371aa6811cef1fe83454691b0dbe2cc9f
 F src/vdbe.h c2d78d15112c3fc5ab87f5e8e0b75d2db1c624409de2e858c3d1aafb1650bb4f
@@ -1543,6 +1543,7 @@ F test/rollback.test 06680159bc6746d0f26276e339e3ae2f951c64812468308838e0a3362d9
 F test/rollback2.test 3f3a4e20401825017df7e7671e9f31b6de5fae5620c2b9b49917f52f8c160a8f
 F test/rollbackfault.test 0e646aeab8840c399cfbfa43daab46fd609cf04a
 F test/round1.test 29c3c9039936ed024d672f003c4d35ee11c14c0acb75c5f7d6188ff16190cfd4
+F test/round2.test d2ab6f54156fe5e4a822e3b5e4de69a4b963439797c2dc516d00ce5fb22b6e17
 F test/rowallock.test 3f88ec6819489d0b2341c7a7528ae17c053ab7cc
 F test/rowhash.test 0bc1d31415e4575d10cacf31e1a66b5cc0f8be81
 F test/rowid.test d27191b5ce794c05bf61081e8b2c546a1844c1641321dcaf7fb785234256cc8e
@@ -2195,8 +2196,11 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 40de3939792e17df25598b3e60d1cebcecde2b00832acd55604f14b21398a9a7
-R 3798b5dfb33f66d6cffbf2ab99073009
+P 56af06fa12104a1fe119d7087746011183af053834eac72d0fb69f60d98054c6
+R d2547c87a8223548d3daae3e983f63e3
+T *branch * round-up
+T *sym-round-up *
+T -sym-trunk *
 U drh
-Z 6cba9a77bc527574a7f08d08f1d8739d
+Z fe023add6bc94c6c74f3cca88a0e7276
 # Remove this line to create a well-formed Fossil manifest.
index d58d474cbf76b53a11f81ba0048a9ffeb908878c..79ca38fb04f070d0fc4deadba9be55af52ccf85d 100644 (file)
@@ -1 +1 @@
-56af06fa12104a1fe119d7087746011183af053834eac72d0fb69f60d98054c6
\ No newline at end of file
+4a790d3b28685f08bbb722057cd6a97aea08a2b2a6098562c6373fd3b5b7206c
\ No newline at end of file
index 0cebb474a21ce1753e07de7a406f74213e62b278..fb25fa4b29138218946b2b41871f16f8a83fe666 100644 (file)
@@ -1011,6 +1011,18 @@ int sqlite3Atoi(const char *z){
   return x;
 }
 
+/*
+** Return true if the first N characters of string z[] are '9'
+*/
+static SQLITE_NOINLINE int allNines(const char *z, int N){
+  int i;
+  assert( N>0 );
+  for(i=0; i<N; i++){
+    if( z[i]!='9' ) return 0;
+  }
+  return 1;
+}
+
 /*
 ** Decode a floating-point value into an approximate decimal
 ** representation.
@@ -1027,6 +1039,21 @@ int sqlite3Atoi(const char *z){
 ** stored in p->z[] which is a often (but not always) a pointer
 ** into the middle of p->zBuf[].  There are p->n significant digits.
 ** The p->z[] array is *not* zero-terminated.
+**
+** Rounding Behavior:
+**
+**   (1)  If the next digit is 3 or less, then truncate.  Do not round.
+**
+**   (2)  If the next digit is 5 or more, then round up.
+**
+**   (3)  Round up if the next digit is a 4 followed by three or
+**        more 9 digits and all digits after the 4 up to the
+**        antipenultimate digit are 9.  Otherwise truncate.
+**
+** Rule (3) is so that things like round(0.15,1) will come out as 0.2
+** even though the stored value for 0.15 is really
+** 0.1499999999999999944488848768742172978818416595458984375 and ought
+** to round down to 0.1.
 */
 void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRound){
   int i;
@@ -1140,8 +1167,10 @@ void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRound){
   if( iRound>0 && (iRound<p->n || p->n>mxRound) ){
     char *z = &p->zBuf[i+1];
     if( iRound>mxRound ) iRound = mxRound;
-    p->n = iRound;
-    if( z[iRound]>='5' ){
+    if( z[iRound]>='5'
+     || (z[iRound]=='4' && p->n>iRound+5
+                        && allNines(&z[iRound+1],p->n-iRound-3))
+    ){
       int j = iRound-1;
       while( 1 /*exit-by-break*/ ){
         z[j]++;
@@ -1149,7 +1178,7 @@ void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRound){
         z[j] = '0';
         if( j==0 ){
           p->z[i--] = '1';
-          p->n++;
+          iRound++;
           p->iDP++;
           break;
         }else{
@@ -1157,7 +1186,8 @@ void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRound){
         }
       }
     }
-  }
+    p->n = iRound;
+ }
   p->z = &p->zBuf[i+1];
   assert( i+p->n < sizeof(p->zBuf) );
   while( ALWAYS(p->n>0) && p->z[p->n-1]=='0' ){ p->n--; }
diff --git a/test/round2.test b/test/round2.test
new file mode 100644 (file)
index 0000000..1847516
--- /dev/null
@@ -0,0 +1,43 @@
+# 2024-06-10
+#
+# 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.
+#
+#***********************************************************************
+# 
+#  https://sqlite.org/forum/forumpost/c0753dfb2d5d7f75
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix round2
+
+load_static_extension db stmtrand
+do_execsql_test 1.1 {
+  WITH RECURSIVE
+    c(n) AS (VALUES(0) UNION ALL SELECT n+1 FROM c WHERE n<100000),
+    r(a,b) AS MATERIALIZED (SELECT stmtrand()%100000, stmtrand()%100000 FROM c),
+    f(x,y,n) AS (
+       SELECT CAST(format('%d.%d5',a,b) AS real),
+              CAST(format('%d.%d6',a,b) AS real),
+               length(format('%d',b)) FROM r)
+    SELECT x, n, round(x,n), round(y,n) FROM f
+     WHERE round(x,n)<>round(y,n);
+
+} {}
+do_execsql_test 1.2 {
+  SELECT round(0.15,1);
+} 0.2
+do_execsql_test 1.3 {
+  SELECT round(0.14999999999999999,1);
+} 0.2
+do_execsql_test 1.4 {
+  SELECT round(0.1499999999999999944488848768742172978818416595458984375,1);
+} 0.2
+
+
+finish_test