数值类型
固定宽度整数类型
TINYINT、SMALLINT、INTEGER、BIGINT 和 HUGEINT 用于存储整数(即不包含小数部分的数字),各自支持不同范围。若写入超出允许范围的值,会报错。
UTINYINT、USMALLINT、UINTEGER、UBIGINT 和 UHUGEINT 用于存储无符号整数。若写入负数或超出允许范围的值,会报错。
| 名称 | 别名 | 最小值 | 最大值 | 大小(字节) |
|---|---|---|---|---|
TINYINT | INT1 | - 2^7 | 2^7 - 1 | 1 |
SMALLINT | INT2, INT16, SHORT | - 2^15 | 2^15 - 1 | 2 |
INTEGER | INT4, INT32, INT, SIGNED | - 2^31 | 2^31 - 1 | 4 |
BIGINT | INT8, INT64, LONG | - 2^63 | 2^63 - 1 | 8 |
HUGEINT | INT128 | - 2^127 | 2^127 - 1 | 16 |
UTINYINT | UINT8 | 0 | 2^8 - 1 | 1 |
USMALLINT | UINT16 | 0 | 2^16 - 1 | 2 |
UINTEGER | UINT32 | 0 | 2^32 - 1 | 4 |
UBIGINT | UINT64 | 0 | 2^64 - 1 | 8 |
UHUGEINT | UINT128 | 0 | 2^128 - 1 | 16 |
INT8是 64 位整数,并不是UINT8(无符号 8 位整数)的有符号对应类型。INT1、INT2、INT4和INT8这些有符号整数别名继承自 PostgreSQL,在该命名中数字表示 字节数;而无符号对应别名UINT8、UINT16、UINT32、UINT64采用 C/C++ 习惯,数字表示 位数。
INTEGER 通常是最常见选择,在范围、存储大小和性能之间平衡较好。SMALLINT 一般只在磁盘空间极其紧张时使用。BIGINT 和 HUGEINT 适用于 INTEGER 范围不足的场景。
可变长度整数
前面提到的整数类型有一个共同点:其最小值到最大值范围内的所有数字都使用固定存储大小,例如 UTINYINT 为 1 字节、SMALLINT 为 2 字节等。
但有时你需要比 HUGEINT 还大的整数。在这种情况下可使用 BIGNUM 类型。它对正数的存储方式与其他整数类型类似,但会额外使用 3 个字节来存储所需长度与符号位。一个有 N 位十进制数字的数,存储为 BIGNUM 时大约需要 0.415 * N + 3 字节。
与其他系统中的可变长度整数实现不同,BIGNUM 也有上限:可表示的最大/最小值约为 ±4.27e20201778。这相当于 20,201,779 位十进制数字,单个此类数字约需 8 MB 存储空间。
定点小数
DECIMAL(WIDTH, SCALE)(别名 NUMERIC(WIDTH, SCALE))表示精确的定点小数值。创建 DECIMAL 值时,可通过 WIDTH 与 SCALE 指定该字段可容纳的小数范围。WIDTH 决定总位数,SCALE 决定小数点后的位数。例如 DECIMAL(3, 2) 可表示 1.23,但不能表示 12.3 或 1.234。若未指定,默认是 DECIMAL(18, 3)。
两个定点小数做加减乘会返回另一个定点小数,其 WIDTH 与 SCALE 会被调整为足以容纳精确结果;若所需 WIDTH 超过当前支持上限 38,则会报错。
定点小数相除通常不会产生有限小数。因此,Goose 对涉及定点小数的除法统一使用近似的浮点运算,并返回浮点类型结果。
在内部,DECIMAL 会根据 WIDTH 使用不同整数类型存储。
| 宽度 | 内部类型 | 大小(字节) |
|---|---|---|
| 1-4 | INT16 | 2 |
| 5-9 | INT32 | 4 |
| 10-18 | INT64 | 8 |
| 19-38 | INT128 | 16 |
在不需要的情况下使用过大 DECIMAL 会影响性能。尤其是宽度大于 19 的 DECIMAL 较慢,因为 INT128 运算成本明显高于 INT32 或 INT64。因此除非确有必要,建议将 WIDTH 控制在 18 或以下。
浮点类型
FLOAT 与 DOUBLE 是可变精度的数值类型。实际中,这些类型通常实现为 IEEE 754 二进制浮点标准(分别对应单精度和双精度),具体取决于底层处理器、操作系统和编译器支持程度。
| 名称 | 别名 | 描述 |
|---|---|---|
FLOAT | FLOAT4, REAL | 单精度浮点数(4 字节) |
DOUBLE | FLOAT8 | 双精度浮点数(8 字节) |
与定点类型类似,从字面量转换或从其他类型 cast 到浮点类型时,无法精确表示的输入会被近似存储。但哪些输入会受影响往往更难直观判断。例如,1.3::DECIMAL(1, 0) - 0.7::DECIMAL(1, 0) != 0.6::DECIMAL(1, 0) 并不令人意外,但 1.3::FLOAT - 0.7::FLOAT != 0.6::FLOAT 可能会让人意外。
此外,定点小数的加减乘是精确的,而在二进制浮点类型上这些运算都只是近似结果。
不过,对于更复杂的数学运算,内部通常使用浮点运算;若中间步骤 不 被 cast 回与输入/输出同宽度的定点格式,通常能获得更精确结果。例如,(10::FLOAT / 3::FLOAT)::FLOAT * 3 = 10,而 (10::DECIMAL(18, 3) / 3::DECIMAL(18, 3))::DECIMAL(18, 3) * 3 = 9.999。
一般来说:
- 如果你需要精确存储具有确定小数位数的数值,并且要求加减乘精确(如金额),请使用
DECIMAL类型或其别名NUMERIC。 - 如果你需要快速或复杂计算,浮点类型可能更合适。但若结果用于关键场景,建议仔细评估实现中的边界情况(范围、无穷大、下溢、非法操作等),这些处理方式可能与你预期不同,并应了解常见浮点陷阱。可参考 David Goldberg 的文章 “What Every Computer Scientist Should Know About Floating-Point Arithmetic” 以及 Bruce Dawson 博客中的浮点系列。
在大多数平台上,FLOAT 的范围至少为 1E-37 到 1E+37,精度至少约 6 位十进制;DOUBLE 的典型范围约为 1E-307 到 1E+308,精度至少约 15 位。超出这些范围的正数(以及对应镜像范围外的负数)在某些平台可能报错,但通常会分别转换为 0 或无穷大。
除普通数值外,浮点类型还支持多个 IEEE 754 特殊值:
Infinity:正无穷-Infinity:负无穷NaN:非数值
在具备所需 CPU/FPU 支持的机器上,Goose 对这些特殊值基本遵循 IEEE 754,但有两个例外:
NaN与NaN比较为相等,且大于任何其他浮点数。- 某些浮点函数(如
sqrt/sin/asin)对超出定义域的值会报错,而不是返回NaN。
在 SQL 中以字面量插入这些值时,必须加引号;Infinity 可缩写为 Inf;大小写不限。例如:
SELECT
sqrt(2) > '-inf',
'nan' > sqrt(2);
(sqrt(2) > '-inf') | ('nan' > sqrt(2)) |
|---|---|
| true | true |
全局唯一标识符(UUID)
Goose 通过 UUID 类型支持全局唯一标识符(UUID)。
UUID 使用 128 位,在内部表示为 HUGEINT 值。
输出时使用小写十六进制并以连字符分隔,格式为:⟨12345678⟩-⟨1234⟩-⟨1234⟩-⟨1234⟩-⟨1234567890ab⟩
(含连字符共 36 个字符)。例如,4ac7a9e9-607c-4c8a-84f3-843f0191e3fd 是合法 UUID。
Goose 支持生成 UUIDv4 与 UUIDv7。
要获取 UUID 值的版本,可使用 uuid_extract_version function。
UUIDv4
生成 UUIDv4 可使用 uuid() function,或其别名 uuidv4() 与 gen_random_uuid()。
UUIDv7
生成 UUIDv7 可使用 uuidv7() function。
要从 UUIDv7 值中提取时间戳,可使用 uuid_extract_timestamp function:
SELECT uuid_extract_timestamp(uuidv7()) AS ts;
| ts |
|---|
| 2025-04-19 15:51:20.07+00 |