SQLite存储加密货币高精度小数疑问:为何仅保留16位有效数字?
问题原因
SQLite是动态类型+类型亲和性的数据库,和SQL Server这类强类型数据库逻辑不同:
- 你指定的
DECIMAL、REAL、CURRENCY、MONEY等类型,默认会触发NUMERIC类型亲和性,但如果未启用SQLite的DECIMAL扩展,SQLite会将这类数值存储为REAL(即64位双精度浮点数)。 - 双精度浮点数的有效数字仅为15-17位,你的输入
123456789111.12345678有20位有效数字,超出了double的精度范围,因此会被四舍五入到16位有效数字存储。
解决方案
针对加密货币这类需要高精度的财务数据,有三种可靠的处理方式:
1. 用TEXT类型存储(最简便通用)
直接将数值以字符串形式存入TEXT列,完全保留精度,C#中可直接将decimal类型转字符串插入,读取时再转回decimal:
CREATE TABLE datas ( id INTEGER PRIMARY KEY AUTOINCREMENT, price TEXT NOT NULL );
C#代码示例:
// 插入数据 using var cmd = connection.CreateCommand(); cmd.CommandText = "INSERT INTO datas (price) VALUES (@price)"; cmd.Parameters.AddWithValue("@price", yourDecimalValue.ToString(CultureInfo.InvariantCulture)); cmd.ExecuteNonQuery(); // 读取数据 using var cmd = connection.CreateCommand(); cmd.CommandText = "SELECT price FROM datas WHERE id = @id"; cmd.Parameters.AddWithValue("@id", 1); var priceStr = cmd.ExecuteScalar() as string; var price = decimal.Parse(priceStr, CultureInfo.InvariantCulture);
2. 启用SQLite的DECIMAL扩展(原生数值类型支持)
SQLite 3.38.0及以上版本支持原生DECIMAL类型,但需要编译时开启SQLITE_ENABLE_DECIMAL_EXTERNAL_SCHEMA选项。如果使用C#的Microsoft.Data.Sqlite或System.Data.SQLite库,部分预编译版本已包含该扩展:
- 建表时明确指定高精度类型:
CREATE TABLE datas ( id INTEGER PRIMARY KEY AUTOINCREMENT, price DECIMAL(28,8) NOT NULL );
- C#中直接使用
decimal参数插入,无需额外转换,库会自动处理高精度存储。
3. 拆分整数和小数部分(适合频繁数值计算场景)
将价格拆分为整数部分和小数部分(乘以10^8后转整数),用两个INTEGER列存储,彻底避免浮点精度问题:
CREATE TABLE datas ( id INTEGER PRIMARY KEY AUTOINCREMENT, price_int INTEGER NOT NULL, -- 整数部分 price_frac INTEGER NOT NULL -- 小数部分*100000000 );
C#代码示例:
// 拆分decimal为整数和小数部分 var price = 123456789111.12345678m; var priceInt = (long)Math.Truncate(price); var priceFrac = (long)((price - priceInt) * 100000000); // 插入 using var cmd = connection.CreateCommand(); cmd.CommandText = "INSERT INTO datas (price_int, price_frac) VALUES (@int, @frac)"; cmd.Parameters.AddWithValue("@int", priceInt); cmd.Parameters.AddWithValue("@frac", priceFrac); cmd.ExecuteNonQuery(); // 读取并组合 using var cmd = connection.CreateCommand(); cmd.CommandText = "SELECT price_int, price_frac FROM datas WHERE id = @id"; cmd.Parameters.AddWithValue("@id", 1); using var reader = cmd.ExecuteReader(); if (reader.Read()) { var intPart = reader.GetInt64(0); var fracPart = reader.GetInt64(1); var totalPrice = intPart + (decimal)fracPart / 100000000; }
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

