Index: src/func.c ================================================================== --- src/func.c +++ src/func.c @@ -459,17 +459,18 @@ 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); + sqlite3 *db = sqlite3_context_db_handle(context); + zBuf = sqlite3MPrintf(db,"%!.*f",n,r); if( zBuf==0 ){ sqlite3_result_error_nomem(context); return; } sqlite3AtoF(zBuf, &r, sqlite3Strlen30(zBuf), SQLITE_UTF8); - sqlite3_free(zBuf); + sqlite3DbFreeNN(db, zBuf); } sqlite3_result_double(context, r); } #endif Index: src/printf.c ================================================================== --- src/printf.c +++ src/printf.c @@ -481,10 +481,11 @@ case etEXP: case etGENERIC: { FpDecode s; int iRound; int j; + int mx; if( bArgList ){ realvalue = getDoubleArg(pArgList); }else{ realvalue = va_arg(ap,double); @@ -501,11 +502,16 @@ if( precision==0 ) precision = 1; iRound = precision; }else{ iRound = precision+1; } - sqlite3FpDecode(&s, realvalue, iRound, flag_altform2 ? 26 : 16); + if( flag_altform2 ){ + mx = 26 + ((pAccum->printfFlags & SQLITE_PRINTF_INTERNAL)!=0); + }else{ + mx = 16; + } + sqlite3FpDecode(&s, realvalue, iRound, mx); if( s.isSpecial ){ if( s.isSpecial==2 ){ bufpt = flag_zeropad ? "null" : "NaN"; length = sqlite3Strlen30(bufpt); break; Index: src/util.c ================================================================== --- src/util.c +++ src/util.c @@ -1008,10 +1008,27 @@ int sqlite3Atoi(const char *z){ int x = 0; sqlite3GetInt32(z, &x); return x; } + +/* +** z[] is the complete list of digits for a floating point conversion. +** The z[iRound] character is a 4. This routine checks to see if the +** iRound-1 character should be rounded up even though z[iRound] is not +** a 5. +** +** Return true if the 4 is followed by at least three 9s and all digits +** past the 4 are 9s out to the limit of precision. +*/ +static SQLITE_NOINLINE int shouldRoundUp(const char *z, int n, int iRound){ + int i; + assert( z[iRound]=='4' ); + for(i=iRound+1; i15; +} /* ** Decode a floating-point value into an approximate decimal ** representation. ** @@ -1025,10 +1042,28 @@ ** ** The significant digits of the decimal representation are ** 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 significant digits after the 4 are +** 9 and mxRound is 27. Otherwise truncate. +** +** Rule (3) is used by the built-in round() function to do more aggressive +** rounding 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. Rule (3) is only applied if mxRound==27. +** And mxRound is only 27 if the internal sqlite3MPrintf() formatter is +** used and the "!" flag is included. */ void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRound){ int i; u64 v; int e, exp = 0; @@ -1138,28 +1173,30 @@ } } if( iRound>0 && (iRoundn || 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' && mxRound>=27 && shouldRoundUp(z, p->n, iRound)) + ){ int j = iRound-1; while( 1 /*exit-by-break*/ ){ z[j]++; if( z[j]<='9' ) break; z[j] = '0'; if( j==0 ){ p->z[i--] = '1'; - p->n++; + iRound++; p->iDP++; break; }else{ j--; } } } - } + 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--; } } ADDED test/round2.test Index: test/round2.test ================================================================== --- /dev/null +++ test/round2.test @@ -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