Index: src/func.c ================================================================== --- src/func.c +++ src/func.c @@ -433,44 +433,69 @@ } sqlite3_result_blob64(context, (char*)&z[p1], (u64)p2, SQLITE_TRANSIENT); } } +/* +** The library round() function is only available if +** SQLITE_ENABLE_MATH_FUNCTIONS is defined. Without that macro, we +** have to grow our own. +** +** The sqlite3Round(x) routine only needs to deal with non-negative +** numbers. +*/ +#ifdef SQLITE_ENABLE_MATH_FUNCTIONS +# define sqlite3Round(X) round(X) +#else +static double sqlite3Round(double x){ + assert( x>=0.0 ); + if( x>+4503599627370496.0 ){ + return x; + }else{ + sqlite3_int64 ii = (sqlite3_int64)(x+0.5); + return (double)ii; + } +} +#endif + +/* +** This is the value of the least significant bit of the significand +** relative to the total magnitude of the number for an IEEE754 binary64. +** Since the significant is 53 bits, this is pow(2,-52). +*/ +#define SQLITE_DOUBLE_EPSILON 2.220446049250313080847263336181640625e-16 + /* ** Implementation of the round() function */ #ifndef SQLITE_OMIT_FLOATING_POINT static void roundFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ - int n = 0; - double r; - char *zBuf; + int n = 0; /* Second argument. Digits to the right of decimal point */ + double r; /* First argument. Value to be rounded */ + double rX = 1.0; /* Scaling factor. pow(10,n) */ + double rSgn; /* Sign of the first argument */ assert( argc==1 || argc==2 ); + if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; + r = sqlite3_value_double(argv[0]); + if( r<0 ){ + rSgn = -1.0; + r = -r; + }else{ + rSgn = 1.0; + } if( argc==2 ){ + double rY = 10.0; /* Use to compute rX */ + int i; /* Loop counter for computing rX */ if( SQLITE_NULL==sqlite3_value_type(argv[1]) ) return; n = sqlite3_value_int(argv[1]); if( n>30 ) n = 30; if( n<0 ) n = 0; - } - if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; - r = sqlite3_value_double(argv[0]); - /* If Y==0 and X will fit in a 64-bit int, - ** handle the rounding directly, - ** otherwise use printf. - */ - if( r<-4503599627370496.0 || r>+4503599627370496.0 ){ - /* The value has no fractional part so there is nothing to round */ - }else if( n==0 ){ - r = (double)((sqlite_int64)(r+(r<0?-0.5:+0.5))); - }else{ - zBuf = sqlite3_mprintf("%!.*f",n,r); - if( zBuf==0 ){ - sqlite3_result_error_nomem(context); - return; - } - sqlite3AtoF(zBuf, &r, sqlite3Strlen30(zBuf), SQLITE_UTF8); - sqlite3_free(zBuf); - } + for(i=n, rY=10; i>0; i>>=1, rY=rY*rY){ + if( i&1 ) rX *= rY; + } + } + r = rSgn*sqlite3Round(r*rX + rX*r*SQLITE_DOUBLE_EPSILON)/rX; sqlite3_result_double(context, r); } #endif /* @@ -2503,11 +2528,10 @@ ){ assert( argc==0 ); (void)argv; sqlite3_result_double(context, M_PI); } - #endif /* SQLITE_ENABLE_MATH_FUNCTIONS */ /* ** Implementation of sign(X) function. */ ADDED test/round2.test Index: test/round2.test ================================================================== --- /dev/null +++ test/round2.test @@ -0,0 +1,50 @@ +# 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 ieee754 +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 +do_execsql_test 1.5 { + SELECT round(ieee754_from_blob(x'3fc3333333333333'),1); +} 0.2 +do_execsql_test 1.6 { + SELECT round(ieee754_from_blob(x'3fc3333333333332'),1); +} 0.1 + + + +finish_test