小巧、快速、可靠。
三者选其二。
浮点数

1. SQLite 如何存储数字

SQLite 将整数存储在 64 位 二进制补码 格式¹ 中。这使得存储范围为 -9223372036854775808 到 +9223372036854775807(含)。此范围内的整数是精确的。

所谓的“REAL”或浮点值存储在 IEEE 754 Binary-64 格式¹ 中。这使得正值范围大约在 1.7976931348623157e+308 和 4.9406564584124654e-324 之间,负值范围与之等效。Binary64 也可能是 0.0(和 -0.0)、正负无穷大以及“NaN”或“非数字”。浮点值是近似的。

请仔细注意上一段中的最后一句话。

浮点值是近似的。

如果您需要精确答案,则不应在 SQLite 或任何其他产品中使用 Binary64 浮点值。这不是 SQLite 的限制。这是浮点数设计中固有的数学限制。


¹ 异常:R-Tree 扩展 将信息存储为 32 位浮点数或整数。

1.1. 浮点数精度

SQLite 保证保留浮点值的 15 个最重要的数字。但是,它不保证对浮点值进行计算的准确性,因为无法保证这种准确性。对浮点值进行数学运算会引入误差。例如,考虑一下尝试减去两个大小相似的浮点数时会发生什么。

1152693165.1106291898
-1152693165.1106280772

0.0000011126

上面显示的结果 (0.0000011126) 是正确答案。但是,如果您使用 Binary64 浮点数进行此计算,则获得的答案为 0.00000095367431640625 - 误差约为 14%。如果您在程序中执行许多类似的计算,这些误差会累积起来,导致最终结果毫无意义。

误差产生于每个数字只有大约前 15 个有效数字被准确地存储,而两个被减数字之间的第一个差异是在第 16 位。

1.2. 浮点数

Binary64 浮点格式使用每个数字 64 位。因此,有 1.845e+19 个不同的浮点值。另一方面,1.7977e+308 和 4.9407e-324 范围内的实数有无限多个。由此可知,Binary64 无法表示该范围内的所有可能的实数。需要近似值。

IEEE 754 浮点值是乘以 2 的幂的整数

M × 2E

M 值是“尾数”,E 是“指数”。M 和 E 都是整数。

对于 Binary64,M 是一个 53 位整数,E 是一个 11 位整数,它被偏移以表示 -1074 到 +972(含)之间的值范围。

(注:IEEE 754 的通常描述更为复杂,如果您真的想了解 IEEE 754 的细节、优点和局限性,理解额外的复杂性非常重要。但是,这里显示的整数描述虽然不完全正确,但更容易理解,足以满足本文的目的。)

1.2.1. 不可表示的数字

并非所有小于 16 位有效数字的十进制数都可以被精确地表示为 Binary64 数。事实上,大多数小数点右边的数字的十进制数没有精确的 Binary64 等效值。例如,如果您有一个数据库列,用于保存以美元和美分表示的商品价格,那么唯一可以被精确表示的美分值为 0.00、0.25、0.50 和 0.75。小数点右边的任何其他数字都会导致近似值。如果您提供“价格”值为 47.49,该数字将在 Binary64 中表示为

6683623321994527 × 2-47

结果为

47.49000000000000198951966012828052043914794921875

该数字非常接近 47.49,但并不精确。它稍微大了一点。如果我们将 M 减 1 为 6683623321994526,以便我们拥有下一个较小的可能 Binary64 值,我们将得到

47.4899999999999948840923025272786617279052734375

第二个数字太小了。第一个数字更接近目标值 47.49,因此被使用。但是,它并不精确。大多数十进制值在 IEEE 754 中都以这种方式工作。请记住我们上面提到的关键点。

浮点值是近似的。

如果您对浮点值一无所知,请不要忘记这条关键思想。

1.2.2. 足够接近了吗?

IEEE 754 Binary64 提供的精度足以满足大多数计算。例如,如果“47.49”表示价格,而通货膨胀率为每年 2%,那么价格每秒上涨约 0.0000000301 美元。记录值 47.49 中的误差代表了大约 66 纳秒的通货膨胀。因此,如果您在输入时 47.49 的价格是精确的,那么通货膨胀的影响将导致实际值在不到十亿分之一秒内精确等于实际存储的值 (47.4900000000000019895196601282805204391479492187)。对于大多数目的而言,这种精度水平应该足够了吧?

2. 处理浮点数的扩展

2.1. ieee754.c 扩展

ieee754 扩展在浮点数的 Binary64 表示和 M×2E 格式之间转换浮点数。换句话说,在表达式中

F = M × 2E

ieee754 扩展在 F 和 (M,E) 之间进行转换,反之亦然。

ieee754 扩展不是 合并 的一部分,但默认情况下包含在 CLI 中。如果您想在应用程序中包含 ieee754 扩展,则需要单独编译和加载它。

2.1.1. ieee754() 函数

ieee754(F) SQL 函数以单个浮点值作为输入,并返回一个类似于以下形式的字符串

'ieee754(M,E)'

除了 M 和 E 被替换为浮点值的尾数和指数。例如

sqlite> .mode box
sqlite> SELECT ieee754(47.49) AS x;
┌───────────────────────────────┐
│               x               │
├───────────────────────────────┤
│ ieee754(6683623321994527,-47) │
└───────────────────────────────┘

反过来,ieee754() 的双参数版本接受 M 和 E 值,并将它们转换为相应的 F 值

sqlite> select ieee754(6683623321994527,-47) as x;
┌───────┐
│   x   │
├───────┤
│ 47.49 │
└───────┘

2.1.2. ieee754_mantissa() 和 ieee754_exponent() 函数

ieee754() 的单参数形式的文本输出非常适合人类阅读,但作为更大表达式的一部分使用起来很麻烦。因此,添加了 ieee754_mantissa() 和 ieee754_exponent() 例程,以返回对应于其单参数 F 值的 M 和 E 值。例如

sqlite> .mode box
sqlite> SELECT ieee754_mantissa(47.49) AS M, ieee754_exponent(47.49) AS E;
┌──────────────────┬─────┐
│        M         │  E  │
├──────────────────┼─────┤
│ 6683623321994527 │ -47 │
└──────────────────┴─────┘

2.1.3. ieee754_from_blob() 和 ieee754_to_blob() 函数

ieee754_to_blob(F) SQL 函数将浮点数 F 转换为一个 8 字节 BLOB,它是该数字的大端序 Binary64 编码。ieee754_from_blob(B) 函数反过来进行,将一个 8 字节 blob 转换为 Binary64 编码所代表的浮点值。

因此,例如,如果您在 维基百科 上阅读到最小的正 Binary64 值的编码是 0x0000000000000001,那么您可以像这样找到相应的浮点值

sqlite> .mode box
sqlite> SELECT ieee754_from_blob(x'0000000000000001') AS F;
┌───────────────────────┐
│           F           │
├───────────────────────┤
│ 4.94065645841247e-324 │
└───────────────────────┘

或者反过来

sqlite> .mode box
sqlite> SELECT quote(ieee754_to_blob(4.94065645841247e-324)) AS binary64;
┌─────────────────────┐
│      binary64       │
├─────────────────────┤
│ X'0000000000000001' │
└─────────────────────┘

2.2. decimal.c 扩展

decimal 扩展为存储为文本字符串的数字提供任意精度的十进制运算。由于数字存储为任意精度和文本形式,因此不需要近似值。可以精确地进行计算。

decimal 扩展目前(目前)不是 SQLite 合并 的一部分。但是,它包含在 CLI 中。

有三个数学函数可用

前三个函数分别将它们的论证相加、相减和相乘,并返回一个新的文本字符串,它是结果的十进制表示。论证被解释为文本。没有除法运算符,因为十进制除法通常不会生成有限的十进制结果。

decimal_pow2(N) 函数返回 2.0 的 N 次方,其中 N 是 -20000 到 +20000 之间的整数。

使用 decimal_cmp(A,B) 来比较两个十进制值。如果 A 分别小于、等于或大于 B,则结果为负数、零或正数。

decimal_sum(X) 函数是一个聚合函数,类似于内置的 sum() 聚合函数,只是 decimal_sum() 以任意精度计算其结果,因此是精确的。

decimal 扩展提供“decimal”排序规则,它按数值顺序比较十进制文本字符串。

decimal(X) 和 decimal_exp(X) 为输入 X 生成十进制表示。decimal_exp(X) 函数以科学记数法(末尾有“e+NN”)返回结果,而 decimal(X) 返回纯十进制数(没有“e+NN”)。如果输入 X 是浮点值,则将其扩展为其精确的十进制等效值。例如

sqlite> .mode qbox
sqlite> select decimal(47.49);
┌──────────────────────────────────────────────────────┐
│                    decimal(47.49)                    │
├──────────────────────────────────────────────────────┤
│ '47.49000000000000198951966012828052043914794921875' │
└──────────────────────────────────────────────────────┘

此页面最后修改于 2024-07-25 10:34:20 UTC