From 8642ca202539414b482d0b806c77c721e38c3ef6 Mon Sep 17 00:00:00 2001 From: drh <> Date: Sat, 21 Feb 2026 19:26:58 +0000 Subject: [PATCH] New sqlite3_db_config(SQLITE_DBCONFIG_FP_DIGITS) that let's the application specify the number of significant digits that double→text conversions will attempt to preserve. FossilOrigin-Name: 3f16985dcc47a366b54164c5024f920a79dddd76faeac5e36b4770732ed72c0a --- manifest | 24 ++++++++++++------------ manifest.uuid | 2 +- src/main.c | 9 +++++++++ src/shell.c.in | 15 ++++++++++++--- src/sqlite.h.in | 18 +++++++++++++++++- src/sqliteInt.h | 1 + src/test1.c | 1 + src/vdbemem.c | 15 ++++++++++----- test/fpconv1.test | 40 ++++++++++++++++++++++++++-------------- 9 files changed, 89 insertions(+), 36 deletions(-) diff --git a/manifest b/manifest index c3151a0aaa..2bddd7d3ad 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C New\stest\scases\sfor\sfloating-point\sconversions. -D 2026-02-21T13:57:40.503 +C New\ssqlite3_db_config(SQLITE_DBCONFIG_FP_DIGITS)\sthat\slet's\sthe\sapplication\nspecify\sthe\snumber\sof\ssignificant\sdigits\sthat\sdouble→text\sconversions\nwill\sattempt\sto\spreserve. +D 2026-02-21T19:26:58.856 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -702,7 +702,7 @@ F src/insert.c dfd311b0ac2d4f6359e62013db67799757f4d2cc56cca5c10f4888acfbbfa3fd F src/json.c 8b6341a419150b28530cc21e3951b2238c35cdc312f11b2ca29017fe4b1dedc0 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa F src/loadext.c 56a542244fbefc739a2ef57fac007c16b2aefdb4377f584e9547db2ce3e071f9 -F src/main.c e95aa130478fc98a49181ddf094baab45f319286411129253618efe0008f0dc4 +F src/main.c 211f7721b191523b815dee6c6a1e9a5f3ebc052a0ddaaed24a75cf20bf9d4b06 F src/malloc.c 422f7e0498e1c9ef967f06283b6f2c0b16db6b905d8e06f6dbc8baaa3e4e6c5a F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c 3bb59158c38e05f6270e761a9f435bf19827a264c13d1631c58b84bdc96d73b2 @@ -739,17 +739,17 @@ F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 928ff887f2a7c64275182060d94d06fdddbe32226c569781cf7e7edc6f58d7fd F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 F src/select.c 615d62112f5c14fb24facf9391492b42403875bfd4288db6ba10d7e6fbc22c4c -F src/shell.c.in 15285c21cc3f1da9289b0b6c5fd0b2ca8ab2e664b4b300c404afe7634ce9876f -F src/sqlite.h.in 8bcbaecfe2cbecf8c5c1381354fcdd7d307443e88b4953fccb222456c1267b61 +F src/shell.c.in 3e87584890a4e9797865e4771689d8d1aca3b0f824f973192784716ecfa320a2 +F src/sqlite.h.in b8a0b4dd92b1e52caa9676e39971bdaaf44eef30c4b148c9c2fa99e88ed6cf3a F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 F src/sqlite3ext.h 1b7a0ee438bb5c2896d0609c537e917d8057b3340f6ad004d2de44f03e3d3cca -F src/sqliteInt.h 347722b895995dc3015147150c1f9fed5224748c26b36d69ef405656d1603100 +F src/sqliteInt.h ffa3071dc3b9172acfe1a4edd8bc699928b0d5e02f0b8b9067676aa8e5f8f787 F src/sqliteLimit.h 904a3f520362c7065c18165aaabd504fb13cc1b76cb411f38bd41ac219e4af1e F src/status.c 7565d63a79aa2f326339a24a0461a60096d0bd2bce711fefb50b5c89335f3592 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 F src/tclsqlite.c 85b5a20df96016e5d1d8fdc68c8a4c279c5b93e2049b77cd806c2cc50b9d8c56 F src/tclsqlite.h 614b3780a62522bc9f8f2b9fb22689e8009958e7aa77e572d0f3149050af348a -F src/test1.c 302cc00a5f0bbfa36d73b299d600c073b02ffa7f2b59fd2c81091983ccd574a8 +F src/test1.c 3e3b013f59ffcb57dce00c90d55907072d71d4e970cb0a590cb261efe11bae9c F src/test2.c 62f0830958f9075692c29c6de51b495ae8969e1bef85f239ffcd9ba5fb44a5ff F src/test3.c 432646f581d8af1bb495e58fc98234380250954f5d5535e507fc785eccc3987a F src/test4.c 0ac87fc13cdb334ab3a71823f99b6c32a6bebe5d603cd6a71d84c823d43a25a0 @@ -811,7 +811,7 @@ F src/vdbeInt.h 42488247a80cd9d300627833c6c85ace067ae5011a99e7614e2358130d62feea F src/vdbeapi.c 6cdcbe5c7afa754c998e73d2d5d2805556268362914b952811bdfb9c78a37cf1 F src/vdbeaux.c 396d38a62a357b807eabae0cae441fc89d2767a57ab08026b7072bf7aa2dd00c F src/vdbeblob.c b3f0640db9642fbdc88bd6ebcc83d6009514cafc98f062f675f2c8d505d82692 -F src/vdbemem.c 61b014628c6db12dd10b094b84f3e83ea0f85a1c485891bf0b5f370f3625d4ca +F src/vdbemem.c 565c214150dbab0678bfb04db5488f1788df87aad4ec3ec71cc61e0eb3f18f38 F src/vdbesort.c b69220f4ea9ffea5fdef34d968c60305444eea909252a81933b54c296d9cca70 F src/vdbetrace.c 49e689f751505839742f4a243a1a566e57d5c9eaf0d33bbaa26e2de3febf7b41 F src/vdbevtab.c fc46b9cbd759dc013f0b3724549cc0d71379183c667df3a5988f7e2f1bd485f3 @@ -1131,7 +1131,7 @@ F test/fordelete.test ba98f14446b310f9c9d935b97ec748753d0144a28b356ba30d1f4f6958 F test/fork-test.c 9ac2e6423a1d38df3d6be0e8ac15608b545de21e2b19d9d876254c5931b63edb F test/format4.test eeae341953db8b6bda7f549044797c3278a6cc345d11ada81471671b654f8ef4 F test/fp-speed-1.c b37de94eba034e1703668816225f54510ec60fb0685406608cc707afe6b8234d -F test/fpconv1.test d9e1e1bdeaf22b2e800aa6847466468a930f6aea6b59eea348711f9445a043b1 +F test/fpconv1.test 63f352682fa65601a326563ad633086df6ab194e6ed5e7366786f38a525a7fd7 F test/fts-9fd058691.test 78b887e30ae6816df0e1fed6259de4b5a64ad33c F test/fts3.test 672a040ea57036fb4b6fdc09027c18d7d24ab654 F test/fts3_common.tcl dffad248f9ce090800e272017d2898005c28ee6314fc1dd5550643a02666907a @@ -2195,8 +2195,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P 93f90eacc0c5b2ae0042ec525359298883f8473e24967208feef4029d9fa2d08 -R e68f89bae9b5fbd05cf0e0e735a13171 +P 3033fe97b14ba0531278d4aa444bc5340e044b87a72b3a4341032ddee442000f +R 4d154d27dd2b7e6c72267982eae3c18d U drh -Z 5a9c96d60317e775f664966a4b06b2ad +Z 47c16a6580f94070b6e59ea57711afd7 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index f51a8bda13..fffbbc32ca 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -3033fe97b14ba0531278d4aa444bc5340e044b87a72b3a4341032ddee442000f +3f16985dcc47a366b54164c5024f920a79dddd76faeac5e36b4770732ed72c0a diff --git a/src/main.c b/src/main.c index 37e6f23744..a3bfe6d137 100644 --- a/src/main.c +++ b/src/main.c @@ -971,6 +971,14 @@ int sqlite3_db_config(sqlite3 *db, int op, ...){ rc = setupLookaside(db, pBuf, sz, cnt); break; } + case SQLITE_DBCONFIG_FP_DIGITS: { + int nIn = va_arg(ap, int); + int *pOut = va_arg(ap, int*); + if( nIn>3 && nIn<24 ) db->nFpDigit = (u8)nIn; + if( pOut ) *pOut = db->nFpDigit; + rc = SQLITE_OK; + break; + } default: { static const struct { int op; /* The opcode */ @@ -3399,6 +3407,7 @@ static int openDatabase( db->aDb = db->aDbStatic; db->lookaside.bDisable = 1; db->lookaside.sz = 0; + db->nFpDigit = 17; assert( sizeof(db->aLimit)==sizeof(aHardLimit) ); memcpy(db->aLimit, aHardLimit, sizeof(db->aLimit)); diff --git a/src/shell.c.in b/src/shell.c.in index 05d5831201..8eaa060812 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -9267,6 +9267,7 @@ static int do_meta_command(const char *zLine, ShellState *p){ { "enable_trigger", SQLITE_DBCONFIG_ENABLE_TRIGGER }, { "enable_view", SQLITE_DBCONFIG_ENABLE_VIEW }, { "fts3_tokenizer", SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER }, + { "fp_digits", SQLITE_DBCONFIG_FP_DIGITS }, { "legacy_alter_table", SQLITE_DBCONFIG_LEGACY_ALTER_TABLE }, { "legacy_file_format", SQLITE_DBCONFIG_LEGACY_FILE_FORMAT }, { "load_extension", SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION }, @@ -9283,11 +9284,19 @@ static int do_meta_command(const char *zLine, ShellState *p){ for(ii=0; ii1 && cli_strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue; if( nArg>=3 ){ - sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0); + if( aDbConfig[ii].op==SQLITE_DBCONFIG_FP_DIGITS ){ + sqlite3_db_config(p->db, aDbConfig[ii].op, atoi(azArg[2]), 0); + }else{ + sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0); + } } sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v); - cli_printf(p->out, "%19s %s\n", - aDbConfig[ii].zName, v ? "on" : "off"); + if( aDbConfig[ii].op==SQLITE_DBCONFIG_FP_DIGITS ){ + cli_printf(p->out, "%19s %d\n", aDbConfig[ii].zName, v); + }else{ + cli_printf(p->out, "%19s %s\n", + aDbConfig[ii].zName, v ? "on" : "off"); + } if( nArg>1 ) break; } if( nArg>1 && ii==ArraySize(aDbConfig) ){ diff --git a/src/sqlite.h.in b/src/sqlite.h.in index e1f478a0a9..182f6871c8 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -2648,6 +2648,21 @@ struct sqlite3_mem_methods { ** comments are allowed in SQL text after processing the first argument. ** ** +** [[SQLITE_DBCONFIG_FP_DIGITS]] +**
SQLITE_DBCONFIG_FP_DIGITS
+**
The SQLITE_DBCONFIG_FP_DIGITS option is a small integer is the number +** of significant digits that SQLite attempts to preserve when converting +** floating point numbers (IEEE 754 "doubles") into text. The default value +** (as of SQLite version 3.52.0) is 17.

+** This option takes two arguments which are an integer and a pointer +** to an integer. The first argument is a small integer, between 3 and 20, or +** zero. The FP_DIGITS setting is changed to the small integer, or left +** altered if the first argument is out of range. The second argument is a +** pointer to an integer. If the pointer is not NULL, then the value of +** the FP_DIGITS setting, after possibly being modified by the first +** arguments, is written into the integer to which the second argument points. +**

+** ** ** ** [[DBCONFIG arguments]]

Arguments To SQLITE_DBCONFIG Options

@@ -2692,7 +2707,8 @@ struct sqlite3_mem_methods { #define SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE 1020 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE 1021 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_COMMENTS 1022 /* int int* */ -#define SQLITE_DBCONFIG_MAX 1022 /* Largest DBCONFIG */ +#define SQLITE_DBCONFIG_FP_DIGITS 1023 /* int int* */ +#define SQLITE_DBCONFIG_MAX 1023 /* Largest DBCONFIG */ /* ** CAPI3REF: Enable Or Disable Extended Result Codes diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 8ccb079096..051cb4e874 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -1692,6 +1692,7 @@ struct sqlite3 { u8 noSharedCache; /* True if no shared-cache backends */ u8 nSqlExec; /* Number of pending OP_SqlExec opcodes */ u8 eOpenState; /* Current condition of the connection */ + u8 nFpDigit; /* Significant digits to keep on double->text */ int nextPagesize; /* Pagesize after VACUUM if >0 */ i64 nChange; /* Value returned by sqlite3_changes() */ i64 nTotalChange; /* Value returned by sqlite3_total_changes() */ diff --git a/src/test1.c b/src/test1.c index 230034fa01..3ca5c837a7 100644 --- a/src/test1.c +++ b/src/test1.c @@ -8644,6 +8644,7 @@ static int SQLITE_TCLAPI test_sqlite3_db_config( { "ATTACH_CREATE", SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE }, { "ATTACH_WRITE", SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE }, { "COMMENTS", SQLITE_DBCONFIG_ENABLE_COMMENTS }, + { "FP_DIGITS", SQLITE_DBCONFIG_FP_DIGITS }, }; int i; int v = 0; diff --git a/src/vdbemem.c b/src/vdbemem.c index 48e1014663..9e654858d3 100644 --- a/src/vdbemem.c +++ b/src/vdbemem.c @@ -107,10 +107,11 @@ static void vdbeMemRenderNum(int sz, char *zBuf, Mem *p){ StrAccum acc; assert( p->flags & (MEM_Int|MEM_Real|MEM_IntReal) ); assert( sz>22 ); - if( p->flags & MEM_Int ){ -#if GCC_VERSION>=7000000 + if( p->flags & (MEM_Int|MEM_IntReal) ){ +#if 0 /* Work-around for GCC bug - ** https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96270 */ + ** https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96270. + ** Bug fixed circa 2020, so this work-around removed in 2026. */ i64 x; assert( (p->flags&MEM_Int)*2==sizeof(x) ); memcpy(&x, (char*)&p->u, (p->flags&MEM_Int)*2); @@ -118,10 +119,14 @@ static void vdbeMemRenderNum(int sz, char *zBuf, Mem *p){ #else p->n = sqlite3Int64ToText(p->u.i, zBuf); #endif + if( p->flags & MEM_IntReal ){ + memcpy(zBuf+p->n,".0", 3); + p->n += 2; + } }else{ sqlite3StrAccumInit(&acc, 0, zBuf, sz, 0); - sqlite3_str_appendf(&acc, "%!.17g", - (p->flags & MEM_IntReal)!=0 ? (double)p->u.i : p->u.r); + sqlite3_str_appendf(&acc, "%!.*g", + (p->db ? p->db->nFpDigit : 17), p->u.r); assert( acc.zText==zBuf && acc.mxAlloc<=0 ); zBuf[acc.nChar] = 0; /* Fast version of sqlite3StrAccumFinish(&acc) */ p->n = acc.nChar; diff --git a/test/fpconv1.test b/test/fpconv1.test index 597e065e8b..a93489907e 100644 --- a/test/fpconv1.test +++ b/test/fpconv1.test @@ -17,13 +17,37 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl + +# Unusual rendering cases: +# +do_execsql_test fpconv1-1.0 { + SELECT 1.23 - 2.34; +} {-1.1099999999999999} +# ^--- Not -1.11 as you would expect. -1.11 has a different bit pattern + +do_execsql_test fpconv1-1.1 { + SELECT 1.23 * 2.34; +} {2.8781999999999996} +# ^--- Not 2.8782 as you would expect. 2.8782 has a different bit pattern + +# Change significant digits to 15 and get a different result. +sqlite3_db_config db FP_DIGITS 15 +do_execsql_test fpconv1-1.2 { + SELECT 1.23 - 2.34; +} {-1.11} +do_execsql_test fpconv1-1.3 { + SELECT 1.23 * 2.34; +} {2.8782} +sqlite3_db_config db FP_DIGITS 17 + + if {[catch {load_static_extension db decimal} error]} { puts "Skipping decimal tests, hit load error: $error" finish_test; return } sqlite3_create_function db -do_execsql_test fpconv1-1.0 { +do_execsql_test fpconv1-2.0 { WITH RECURSIVE /* Number of random floating-point values to try. ** On a circa 2021 Ryzen 5950X running Mint Linux, and @@ -49,7 +73,7 @@ do_execsql_test fpconv1-1.0 { load_static_extension db ieee754 -do_execsql_test fpconv1-2.0 { +do_execsql_test fpconv1-3.0 { WITH RECURSIVE c(x,s) AS MATERIALIZED (VALUES(1,random()&0xffefffffffffffff) UNION ALL @@ -71,16 +95,4 @@ do_execsql_test fpconv1-2.0 { } {} # ^---- Values that fail to round-trip will be reported -# Unusual rendering cases: -# -do_execsql_test fpconv1-3.0 { - SELECT 1.23 - 2.34; -} {-1.1099999999999999} -# ^--- Not -1.11 as you would expect. -1.11 has a different bit pattern - -do_execsql_test fpconv1-3.1 { - SELECT 1.23 * 2.34; -} {2.8781999999999996} -# ^--- Not 2.8782 as you would expect. 2.8782 has a different bit pattern - finish_test -- 2.47.3