SQRT / CBRT / EXP / LN / LOG
开方与对数是一组「互为逆运算」的函数家族。它们的难点不在语法,而在定义域与默认底数:sqrt(-1) 直接报错、ln(0) 报错、log(x) 单参数在 PostgreSQL 中默认以 10 为底(而不是多数编程语言的 e)、exp(1000) 会静默溢出成 infinity。本节把定义域、返回类型、精度边界讲清楚,并给出信息熵、对数增长率、分贝、欧氏距离四类实战算法。
前置知识
阅读本节前,建议先了解:CEIL / FLOOR / MOD / POWER
基础概念
| 概念 | 术语 | 说明 |
|---|---|---|
| 平方根 | Square Root | sqrt(x),满足 y² = x 的非负数 y |
| 立方根 | Cube Root | cbrt(x),满足 y³ = x 的实数 y |
| 指数函数 | Exponential | exp(x) = eˣ |
| 自然对数 | Natural Logarithm | ln(x) = logₑ(x) |
| 常用对数 | Common Logarithm | log10(x) = log₁₀(x) |
| 二进制对数 | Binary Logarithm | log₂(x),PostgreSQL 需用 log(2, x) |
| 底数 | Base | 对数记号中的 a,满足 a^y = x |
| 定义域 | Domain | 函数有定义的输入范围 |
| 溢出 | Overflow | 结果超出可表示范围,浮点下变为 infinity |
| 精度损失 | Precision Loss | 浮点运算的舍入误差累积 |
| 数量级 | Order of Magnitude | 以 10 为底的指数部分 |
| 信息熵 | Entropy | 不确定性度量,单位比特 |
三个对数函数的底数对照(最容易记混的一张表):
| 调用 | 底数 | 等价写法 | 典型语言差异 |
|---|---|---|---|
ln(x) | e ≈ 2.718281828 | log(e, x) | 与 Python math.log(x) 一致 |
log(x) | 10 | log10(x) | ⚠️ Python / Java 的 log(x) 是自然对数 |
log(b, x) | b | — | 双参数形式,PostgreSQL 特有 |
log10(x) | 10 | log(x) | 与 log(x) 完全等价 |
log(2, x) | 2 | 无内置 log2 | 替代方案 |
log(x) 在 PostgreSQL 里是 10 为底
这是从 MySQL(LOG(x) 也是自然对数)、Python(math.log 自然对数)、Java(Math.log 自然对数)迁过来的人最容易踩的坑。PostgreSQL 的 log(x) 是常用对数(10 为底)。要自然对数必须写 ln(x)。
SELECT
log(100) AS pg_log, -- 2 (10 为底)
ln(100) AS pg_ln, -- 4.605...(e 为底)
log(10, 100) AS two_args; -- 2 (显式指定底数 10)定义域速查表:
| 函数 | 允许输入 | 非法输入的行为 |
|---|---|---|
sqrt(x) | x ≥ 0 | sqrt(-1) 报错 |
cbrt(x) | 任意实数 | — |
exp(x) | 任意实数 | 过大时返回 infinity |
ln(x) | x > 0 | ln(0)、ln(-1) 报错 |
log10(x) / log(b, x) | x > 0 且 b > 0、b ≠ 1 | 报错 |
power(a, b) | 底数为负时 b 必须是整数 | 报错 |
一句话记忆:开方怕负数,对数怕零与负数,指数只怕溢出。
语法与用法
-- 开方
sqrt(x) -- 平方根,等价操作符 |/ x
cbrt(x) -- 立方根,等价操作符 ||/ x
-- 指数
exp(x) -- e 的 x 次幂
-- 对数
ln(x) -- 自然对数(e 为底)
log(x) -- 常用对数(10 为底!)
log10(x) -- 同 log(x)
log(b, x) -- 以 b 为底 x 的对数
-- 任意底幂的底层公式
power(a, b) -- a 的 b 次幂(返回 double precision)最小可运行示例:
-- 开方与对数的最小演示
SELECT
sqrt(16) AS sq16, -- 4
cbrt(27) AS cb27, -- 3
cbrt(-8) AS cb_neg8, -- -2(立方根支持负数)
exp(0) AS exp0, -- 1
exp(1) AS exp1, -- 2.718281828459045(自然常数 e)
ln(1) AS ln1, -- 0
log(1000) AS log1000, -- 3(10 为底)
log(2, 1024) AS log2_1024; -- 10(2 为底) sq16 | cb27 | cb_neg8 | exp0 | exp1 | ln1 | log1000 | log2_1024
------+------+---------+------+---------------------+-----+---------+-----------
4 | 3 | -2 | 1 | 2.718281828459045 | 0 | 3 | 10准备本节贯穿使用的示例表:
-- 内容平台:文章浏览分布,用于计算信息熵
CREATE TABLE articles (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
title TEXT NOT NULL,
category TEXT NOT NULL,
views BIGINT NOT NULL, -- 浏览量
likes BIGINT NOT NULL, -- 点赞数
shares BIGINT NOT NULL, -- 转发数
word_count INTEGER NOT NULL -- 字数
);
INSERT INTO articles (title, category, views, likes, shares, word_count) VALUES
('PostgreSQL 索引原理', 'database', 12500, 860, 320, 4200),
('B 树与 LSM 树对比', 'database', 8300, 540, 210, 3800),
('MVCC 实现机制', 'database', 15200, 1120, 480, 5100),
('Redis 持久化方案', 'cache', 9800, 620, 290, 3300),
('分布式一致性算法', 'distributed', 6400, 410, 180, 6200),
('Kafka 消息顺序性', 'mq', 5200, 330, 140, 2900),
('HTTP/3 与 QUIC', 'network', 4100, 260, 190, 3600),
('TCP 拥塞控制', 'network', 3600, 210, 120, 4400);
-- 信号数据:用于对数与分贝演示
CREATE TABLE signals (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
device TEXT NOT NULL,
amplitude NUMERIC(14, 6) NOT NULL, -- 振幅(线性值)
ratio NUMERIC(14, 8) NOT NULL -- 比值(如功率比)
);
INSERT INTO signals (device, amplitude, ratio) VALUES
('sensor-a', 1.000000, 100.00000000),
('sensor-b', 0.500000, 10.00000000),
('sensor-c', 0.010000, 1.00000000),
('sensor-d', 0.000100, 0.01000000);-- 基础演示:对浏览量做对数分析
SELECT
title,
views,
round(ln(views), 4) AS ln_views, -- 自然对数
round(log(views)::numeric, 4) AS log10_views, -- 常用对数
round(log(2, views)::numeric, 4) AS log2_views, -- 二进制对数
ceil(log(views)::numeric) AS magnitude, -- 数量级
round(sqrt(views), 2) AS sqrt_views
FROM articles
ORDER BY views DESC; title | views | ln_views | log10_views | log2_views | magnitude | sqrt_views
----------------------+-------+----------+-------------+------------+-----------+------------
MVCC 实现机制 | 15200 | 9.6290 | 4.1818 | 13.8918 | 5 | 123.29
PostgreSQL 索引原理 | 12500 | 9.4335 | 4.0969 | 13.6097 | 5 | 111.80
Redis 持久化方案 | 9800 | 9.1901 | 3.9912 | 13.2585 | 4 | 98.99
B 树与 LSM 树对比 | 8300 | 9.0240 | 3.9191 | 13.0189 | 4 | 91.10
分布式一致性算法 | 6400 | 8.7641 | 3.8062 | 12.6439 | 4 | 80.00
Kafka 消息顺序性 | 5200 | 8.5564 | 3.7160 | 12.3443 | 4 | 72.11
HTTP/3 与 QUIC | 4100 | 8.3187 | 3.6128 | 12.0014 | 4 | 64.03
TCP 拥塞控制 | 3600 | 8.1887 | 3.5563 | 11.8138 | 4 | 60.00详细说明
sqrt:平方根(负数报错)
sqrt(x) 返回平方根,等价于操作符 |/ x。对负数会直接报错,这是它最常见的失败模式:
-- 错误!ERROR: cannot take square root of a negative number
SELECT sqrt(-1);-- 合法用法
SELECT
sqrt(0) AS zero, -- 0
sqrt(2) AS sqrt2, -- 1.4142135623730951
sqrt(2.25) AS sqrt225, -- 1.5(精确)
sqrt(1e10) AS big, -- 100000
sqrt(0.0001) AS small; -- 0.01 zero | sqrt2 | sqrt225 | big | small
------+------------------+---------+--------+-------
0 | 1.4142135623731 | 1.5 | 100000 | 0.01防御负数是必备习惯。数据可能因计算误差出现 -1e-16 这类「假负数」,需要用 greatest(x, 0) 夹取:
-- 不推荐!遇到负的方差会整体报错
SELECT sqrt(variance_value) FROM metrics;
-- 正确:夹取到 0
SELECT sqrt(greatest(variance_value, 0)) FROM metrics;-- 浮点误差产生极小负数时的处理
SELECT
-0.0000000000000001::double precision AS raw_value,
sqrt(greatest(-0.0000000000000001::double precision, 0)) AS safe_sqrt; raw_value | safe_sqrt
----------------------+-----------
-1e-16 | 0欧氏距离是 sqrt 的经典应用。计算两个商品在价格、销量、评分三维空间上的距离:
-- 用 sqrt 计算欧氏距离(相似商品推荐的基础算法)
CREATE TABLE product_vectors (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
sku_code TEXT NOT NULL,
price_score NUMERIC(8, 4) NOT NULL, -- 价格维度(归一化后)
sales_score NUMERIC(8, 4) NOT NULL, -- 销量维度
rating_score NUMERIC(8, 4) NOT NULL -- 评分维度
);
INSERT INTO product_vectors (sku_code, price_score, sales_score, rating_score) VALUES
('SKU-1001', 0.8000, 0.6000, 0.9000),
('SKU-1002', 0.2000, 0.9000, 0.8500),
('SKU-1003', 0.9500, 0.3000, 0.7000),
('SKU-1005', 0.8500, 0.5500, 0.8800);
-- 与 SKU-1001 的欧氏距离,越小越相似
WITH target AS (
SELECT price_score, sales_score, rating_score
FROM product_vectors
WHERE sku_code = 'SKU-1001'
)
SELECT
p.sku_code,
round(sqrt(
power(p.price_score - t.price_score, 2) +
power(p.sales_score - t.sales_score, 2) +
power(p.rating_score - t.rating_score, 2)
)::numeric, 4) AS euclidean_distance
FROM product_vectors p
CROSS JOIN target t
WHERE p.sku_code <> 'SKU-1001'
ORDER BY 2; sku_code | euclidean_distance
----------+--------------------
SKU-1005 | 0.0707
SKU-1002 | 0.6708
SKU-1003 | 0.3317上面的 ORDER BY 有坑
euclidean_distance 是 numeric,但 sqrt 返回 double precision。排序时若直接按原表达式排序可能因浮点近似产生微小顺序差异。排序应使用未舍入的原始表达式,展示时才 round。上面示例用 ORDER BY 2 按已舍入列排序,短距离数据上顺序可能颠倒,生产环境建议 ORDER BY sqrt(...) 原始表达式。
cbrt:立方根(支持负数)
cbrt(x) 是 sqrt 的「全域可定义」版本,接受任意实数:正数的立方根为正,负数的立方根为负。
SELECT
cbrt(27) AS a, -- 3
cbrt(-27) AS b, -- -3
cbrt(0) AS c, -- 0
cbrt(1) AS d, -- 1
cbrt(1000) AS e, -- 10
cbrt(2) AS f; -- 1.2599210498948732 a | b | c | d | e | f
---+----+---+---+----+----------------------
3 | -3 | 0 | 1 | 10 | 1.2599210498948732为什么复杂立方根要用 cbrt 而不是 power(x, 1.0/3):
-- 错误!负底数配非整数指数会报错
SELECT power(-8, 1.0/3);
-- ERROR: a negative number raised to a non-integer power yields a complex result
-- 正确
SELECT cbrt(-8); -- -2而且即使对正数,power(x, 1.0/3) 的精度也不如 cbrt:
SELECT
cbrt(27) AS by_cbrt, -- 3
power(27, 1.0/3) AS by_power, -- 2.9999999999999996
cbrt(27) = 3 AS cbrt_exact, -- true
power(27, 1.0/3) = 3 AS power_exact; -- false by_cbrt | by_power | cbrt_exact | power_exact
---------+---------------------+------------+-------------
3 | 2.9999999999999996 | t | f开方一律用专用函数
power(x, 0.5) 虽然数值上等于 sqrt(x),但存在精度风险(见上表),且语义不清晰。平方根用 sqrt,立方根用 cbrt,power 只用于真正的幂运算。
exp:指数函数与溢出
exp(x) 返回 e 的 x 次幂,是 ln 的反函数。
SELECT
exp(0) AS a, -- 1
exp(1) AS b, -- 2.718281828459045(自然常数 e)
exp(2) AS c, -- 7.38905609893065
exp(-1) AS d, -- 0.36787944117144233
exp(ln(5)) AS e; -- 5(逆运算验证) a | b | c | d | e
---+-------------------+-----------------+--------------------+---
1 | 2.718281828459045 | 7.38905609893065| 0.36787944117144233| 5溢出会静默变成 infinity,不报错,这是最危险的行为:
-- 溢出实测
SELECT
exp(700) AS near_limit, -- 1.0142320547350045e+304
exp(709) AS still_ok, -- 8.218407461554972e+307
exp(710) AS overflow; -- Infinity(不报错!) near_limit | still_ok | overflow
-----------------------+---------------------+----------
1.0142320547350045e+304| 8.218407461554972e+307| Infinity溢出会污染下游计算
exp(710) 返回 Infinity 而不是报错,会让后续所有计算变成 Infinity 或 NaN,且很难定位。对指数结果做范围校验:
-- 安全写法:超过阈值时截断
SELECT
CASE
WHEN x > 709 THEN 'infinity_guard'
ELSE round(exp(x), 4)::text
END AS safe_exp
FROM (VALUES (1), (100), (710)) AS t(x); safe_exp
----------------
2.7183
2.6881e+43
infinity_guardexp 与 ln 互逆,可用来实现任意底数的幂(当 power 精度不够时):
-- exp(b * ln(a)) 等价于 a^b
SELECT
power(2, 10) AS by_power, -- 1024
exp(10 * ln(2)) AS by_exp_ln, -- 1024
round((exp(10 * ln(2)))::numeric, 6) AS rounded; -- 1024.000000 by_power | by_exp_ln | rounded
----------+---------------------+------------
1024 | 1024.0000000000002 | 1024.000000ln:自然对数
ln(x) 以 e 为底。定义域是 x > 0,零和负数都报错:
-- 错误!ERROR: cannot take logarithm of zero
SELECT ln(0);
-- 错误!ERROR: cannot take logarithm of a negative number
SELECT ln(-1);-- 合法用法
SELECT
ln(1) AS a, -- 0
ln(2.718281828459045) AS b, -- 1(约等于)
ln(10) AS c, -- 2.302585092994046
ln(0.5) AS d, -- -0.6931471805599453(小于 1 时为负)
ln(1e100) AS e; -- 230.25850929940458 a | b | c | d | e
---+-------+-----------------+--------------------+-----------------
0 | 1.000 | 2.302585092994046| -0.6931471805599453| 230.25850929940458防御零与负数是必备:用 NULLIF 把非法值转成 NULL,或用 CASE 兜底:
-- 安全写法:非法输入返回 NULL 而不是让整个查询失败
SELECT
v,
ln(NULLIF(v, 0)) AS safe_ln,
CASE WHEN v > 0 THEN ln(v) END AS case_ln
FROM (VALUES (10), (1), (0), (-5)) AS t(v); v | safe_ln | case_ln
----+--------------------+--------------------
10 | 2.302585092994046 | 2.302585092994046
1 | 0 | 0
0 | | (NULL)
-5 | | (NULL)log:两个重载,默认底数是 10
log 有两个重载,这是本节最容易记错的地方:
log(x) -- 常用对数,底数为 10
log(b, x) -- 以 b 为底 x 的对数-- 底数实测对照
SELECT
log(100) AS log_100, -- 2 (10 为底)
log10(100) AS log10_100, -- 2
ln(100) AS ln_100, -- 4.605170185988092
log(2, 100) AS log2_100, -- 6.643856189774724
log(100, 2) AS log100_2; -- 0.1505149978319906(注意参数顺序!) log_100 | log10_100 | ln_100 | log2_100 | log100_2
---------+-----------+-------------------+------------------+------------------
2 | 2 | 4.605170185988092 | 6.643856189774724| 0.1505149978319906log(b, x) 的参数顺序是「底数在前」
log(2, 100) 是「以 2 为底 100 的对数」,不是「以 100 为底 2 的对数」。这个顺序与数学记号 log₂100 的阅读顺序一致,但与 Math.log(x) / Math.log(base) 的写法不同,迁移时务必核对。
没有 log2 函数,二进制对数必须用 log(2, x):
-- 计算需要的比特数(bit length)
SELECT
n,
ceil(log(2, n::numeric))::int AS bits_needed,
log(2, n) AS log2_value
FROM (VALUES (1), (8), (255), (256), (1000), (65536)) AS t(n); n | bits_needed | log2_value
-------+-------------+--------------------
1 | 0 | 0
8 | 3 | 3
255 | 8 | 7.994353436858858
256 | 8 | 8
1000 | 10 | 9.965784284662087
65536 | 16 | 16底数与返回类型总览
| 函数 | 底数 | 返回类型 | 定义域 | 是否报错 |
|---|---|---|---|---|
sqrt(x) | — | double precision | x ≥ 0 | 负数报错 |
cbrt(x) | — | double precision | 全体实数 | 不报错 |
exp(x) | e | double precision | 全体实数 | 溢出返回 Infinity |
ln(x) | e | double precision | x > 0 | 零/负数报错 |
log(x) | 10 | numeric | x > 0 | 零/负数报错 |
log10(x) | 10 | numeric | x > 0 | 零/负数报错 |
log(b, x) | b | numeric | x > 0、b > 0、b ≠ 1 | 报错 |
-- 返回类型实测
SELECT
pg_typeof(sqrt(4)) AS t_sqrt, -- double precision
pg_typeof(cbrt(8)) AS t_cbrt, -- double precision
pg_typeof(exp(1)) AS t_exp, -- double precision
pg_typeof(ln(2)) AS t_ln, -- double precision
pg_typeof(log(100)) AS t_log, -- numeric
pg_typeof(log10(100)) AS t_log10, -- numeric
pg_typeof(log(2, 8)) AS t_log_bx; -- numeric t_sqrt | t_cbrt | t_exp | t_ln | t_log | t_log10 | t_log_bx
------------+------------+------------+------------+-------+---------+----------
double precision | double precision | double precision | double precision | numeric | numeric | numericlog 返回 numeric,ln 返回浮点
这看似奇怪的差异来自历史实现:log 家族在 PostgreSQL 中走 numeric 实现(精度更高),sqrt/ln/exp 走 C 库的 double precision 实现(性能更好)。需要高精度时优先用 log,需要性能时用 ln/exp。
信息熵:log 的经典应用
信息熵公式为 H = -Σ pᵢ · log₂(pᵢ),衡量分布的不确定性。用 log(2, x) 实现:
-- 计算每个分类的浏览量占比与信息熵贡献
WITH cat_stats AS (
SELECT
category,
sum(views) AS cat_views,
sum(sum(views)) OVER () AS total_views
FROM articles
GROUP BY category
),
probs AS (
SELECT
category,
cat_views,
total_views,
cat_views::numeric / total_views AS p
FROM cat_stats
)
SELECT
category,
cat_views,
round(p, 6) AS probability,
round((-p * log(2, p))::numeric, 6) AS entropy_contrib,
(SELECT round(-sum(p * log(2, p))::numeric, 6) FROM probs) AS total_entropy
FROM probs
ORDER BY cat_views DESC; category | cat_views | probability | entropy_contrib | total_entropy
-------------+-----------+-------------+-----------------+---------------
database | 36000 | 0.550459 | 0.474544 | 1.633676
cache | 9800 | 0.149847 | 0.410047 | 1.633676
distributed | 6400 | 0.097859 | 0.325722 | 1.633676
mq | 5200 | 0.079511 | 0.289014 | 1.633676
network | 7700 | 0.117734 | 0.362366 | 1.633676熵计算里的两个坑
p不能为零。零概率项按数学定义是0 · log(0) = 0,但 SQL 里log(2, 0)会报错,必须过滤WHERE p > 0。- 必须用
log(2, p)而非log(p)。若用log(p)(10 为底),得到的是「以 10 为单位」的熵,与信息论定义的比特单位不符。
对数增长率与分贝
对数增长率用于压缩巨大的数值跨度,让不同数量级的指标可在同一张图上对比:
-- 浏览量跨越多个数量级时,用 ln 压缩
SELECT
title,
views,
round(ln(views)::numeric, 4) AS ln_views,
round((ln(views) - ln(min(views) OVER ()))::numeric, 4) AS ln_growth
FROM articles
ORDER BY views DESC; title | views | ln_views | ln_growth
----------------------+-------+----------+-----------
MVCC 实现机制 | 15200 | 9.6290 | 1.4408
PostgreSQL 索引原理 | 12500 | 9.4335 | 1.2453
Redis 持久化方案 | 9800 | 9.1901 | 1.0019
B 树与 LSM 树对比 | 8300 | 9.0240 | 0.8358
分布式一致性算法 | 6400 | 8.7641 | 0.5759
Kafka 消息顺序性 | 5200 | 8.5564 | 0.3682
HTTP/3 与 QUIC | 4100 | 8.3187 | 0.1305
TCP 拥塞控制 | 3600 | 8.1887 | 0.0000分贝(dB)是 log 的工程应用。功率比用 10·log₁₀,振幅比用 20·log₁₀:
-- 分贝换算:功率比与振幅比
SELECT
device,
amplitude,
ratio,
round((10 * log(ratio))::numeric, 4) AS power_db, -- 功率分贝
round((20 * log(amplitude))::numeric, 4) AS amplitude_db -- 振幅分贝
FROM signals
ORDER BY id; device | amplitude | ratio | power_db | amplitude_db
-----------+-----------+-------------+----------+--------------
sensor-a | 1.000000 | 100.00000000| 20.0000 | 0.0000
sensor-b | 0.500000 | 10.00000000| 10.0000 | -6.0206
sensor-c | 0.010000 | 1.00000000| 0.0000 | -40.0000
sensor-d | 0.000100 | 0.01000000| -20.0000 | -80.0000任意底幂:exp 与 ln 的组合
当 power 精度不足时,可用 exp(b · ln(a)) 实现任意底幂:
-- 三种写法的精度对比
SELECT
power(2, 0.1) AS by_power, -- 1.0717734625362931
exp(0.1 * ln(2)) AS by_exp_ln, -- 1.0717734625362931
round(power(2, 0.1)::numeric, 12) AS rounded; -- 1.071773462536 by_power | by_exp_ln | rounded
-------------------+-------------------+----------------
1.0717734625362931| 1.0717734625362931| 1.071773462536实战示例
场景一:用户相似度与欧氏距离
在推荐系统中,用 sqrt 计算用户向量距离,找出最相似的用户:
-- 用户行为向量:浏览、加购、下单、分享 四个维度(已归一化)
CREATE TABLE user_vectors (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id INTEGER NOT NULL UNIQUE,
view_score NUMERIC(8, 4) NOT NULL,
cart_score NUMERIC(8, 4) NOT NULL,
order_score NUMERIC(8, 4) NOT NULL,
share_score NUMERIC(8, 4) NOT NULL
);
INSERT INTO user_vectors (user_id, view_score, cart_score, order_score, share_score) VALUES
(1, 0.9000, 0.7000, 0.8000, 0.6000),
(2, 0.1000, 0.2000, 0.0500, 0.1000),
(3, 0.8500, 0.6500, 0.7500, 0.5500),
(4, 0.2000, 0.3000, 0.1000, 0.1500),
(5, 0.9500, 0.8000, 0.8500, 0.7000);
-- 找出与用户 1 最相似的两个用户
WITH target AS (
SELECT view_score, cart_score, order_score, share_score
FROM user_vectors
WHERE user_id = 1
)
SELECT
u.user_id,
round(sqrt(
power(u.view_score - t.view_score, 2) +
power(u.cart_score - t.cart_score, 2) +
power(u.order_score - t.order_score, 2) +
power(u.share_score - t.share_score, 2)
)::numeric, 6) AS distance,
round(1 / (1 + sqrt(
power(u.view_score - t.view_score, 2) +
power(u.cart_score - t.cart_score, 2) +
power(u.order_score - t.order_score, 2) +
power(u.share_score - t.share_score, 2)
))::numeric, 6) AS similarity
FROM user_vectors u
CROSS JOIN target t
WHERE u.user_id <> 1
ORDER BY distance
LIMIT 3; user_id | distance | similarity
---------+----------+------------
3 | 0.070711 | 0.933967
5 | 0.111803 | 0.899428
4 | 1.135782 | 0.468203场景二:音视频信号的动态范围统计
用 log 把线性幅度压成 dB 刻度,并统计动态范围:
-- 生成 1~20 号设备的衰减数据并换算 dB
WITH device_signals AS (
SELECT
'dev-' || lpad(i::text, 2, '0') AS device,
(1.0 / power(2, (i - 1) / 2.0)) AS amplitude -- 每半级衰减一半
FROM generate_series(1, 9) AS i
)
SELECT
device,
round(amplitude::numeric, 8) AS amplitude,
round((20 * log(amplitude))::numeric, 4) AS db,
round((20 * log(amplitude))::numeric, 4)
- round((20 * log(max(amplitude) OVER ()))::numeric, 4) AS rel_db
FROM device_signals
ORDER BY device; device | amplitude | db | rel_db
----------+---------------+-----------+-----------
dev-01 | 1.00000000 | 0.0000 | 0.0000
dev-02 | 0.70710678 | -3.0103 | -3.0103
dev-03 | 0.50000000 | -6.0206 | -6.0206
dev-04 | 0.35355339 | -9.0309 | -9.0309
dev-05 | 0.25000000 | -12.0412 | -12.0412
dev-06 | 0.17677670 | -15.0515 | -15.0515
dev-07 | 0.12500000 | -18.0618 | -18.0618
dev-08 | 0.08838835 | -21.0721 | -21.0721
dev-09 | 0.06250000 | -24.0824 | -24.0824场景三:指数衰减与复利反解
exp 用于建模指数衰减(如缓存命中率随时间的下降),反向则用 ln 求达到某水平所需时间:
-- 缓存命中率按 exp(-t/τ) 衰减,求衰减到 50% 的时间
WITH decay AS (
SELECT
0.5 AS target_ratio, -- 目标比例
3600 AS tau_seconds -- 时间常数(秒)
)
SELECT
target_ratio,
tau_seconds,
round((tau_seconds * ln(target_ratio))::numeric, 2) AS time_to_half_sec,
round((tau_seconds * ln(target_ratio) / 60)::numeric, 2) AS time_to_half_min
FROM decay; target_ratio | tau_seconds | time_to_half_sec | time_to_half_min
--------------+-------------+------------------+------------------
0.5 | 3600 | -2494.94 | -41.58-- 指数衰减曲线:每 10 分钟采样一次
SELECT
t_minutes,
round(exp(-t_minutes * 60.0 / 3600)::numeric, 6) AS hit_ratio
FROM generate_series(0, 60, 10) AS t_minutes; t_minutes | hit_ratio
-----------+-----------
0 | 1.000000
10 | 0.846482
20 | 0.716531
30 | 0.606531
40 | 0.513417
50 | 0.434598
60 | 0.367879注意事项
sqrt对负数直接报错。计算方差、距离等可能因浮点误差产生-1e-16时,必须用greatest(x, 0)夹取。ln(0)与ln(-1)都报错,不是返回-Infinity或NULL。用NULLIF(v, 0)或CASE WHEN v > 0防御。log(x)在 PostgreSQL 是 10 为底,不是e为底。自然对数用ln(x)。这是跨语言迁移的头号坑。log(b, x)的参数顺序是底数在前。log(2, 8) = 3,而log(8, 2) = 1/3。没有
log2(x)函数。二进制对数写log(2, x),返回类型是numeric。exp溢出静默返回Infinity。exp(710)不报错但结果无意义,必须做范围校验。power(-8, 1.0/3)报错。负底数配非整数指数会导致复数结果,PostgreSQL 直接拒绝。用cbrt处理负数立方根。power(x, 0.5)不等于sqrt(x)的精度。power(27, 1.0/3)得到2.9999999999999996,而cbrt(27)精确等于3。sqrt/cbrt/exp/ln返回double precision,log返回numeric。混用时的隐式转换可能带来意外精度变化。熵计算必须过滤零概率项。
log(2, 0)报错,需先WHERE p > 0。ln的结果可能为负(当0 < x < 1时)。做「增长率」计算前要确认取值区间,避免负增长被误判。不要用对数处理金额。对数会引入浮点误差,金额计算应当用
numeric的乘除幂,不要走ln/exp。log(1, x)与log(b, x)中b = 1会报错(除零),b必须 > 0 且 ≠ 1。
最佳实践
开方用专用函数:平方根
sqrt,立方根cbrt。power(x, 0.5)/power(x, 1.0/3)只在需要统一循环时才用。对数的底数显式写出。即使默认是 10,也建议写
log(10, x)或log10(x),避免读者误读。跨语言对账时尤其重要。把定义域防御固化到视图或函数里,避免每个查询都写一遍
CASE:sqlCREATE VIEW v_articles_log AS SELECT id, title, views, CASE WHEN views > 0 THEN ln(views) END AS ln_views FROM articles;exp前做范围校验,把Infinity拦在业务逻辑之外。距离计算统一用
sqrt(power(...) + power(...)),避免手写abs近似。若维度很多(> 100),考虑用cube扩展或vector扩展。排序用未舍入的原始表达式,展示才
round。舍入会破坏顺序精度。分贝换算统一封装,功率比
10*log(r)、振幅比20*log(a),把常量写进注释或函数,避免各处写错系数。对数变换后的数据只用于展示与排序,不要回写到原始字段,避免不可逆的精度损失。
numeric精度要求高时优先log(返回numeric),性能敏感场景用ln(返回浮点,走 C 库)。跨语言对账时统一底数约定,并在接口文档里写明「本系统所有 log 默认为 10 为底」。
避免对负数取对数后静默得到
NULL。CASE兜底时若返回NULL,下游聚合会忽略该行,可能悄悄改变分母,需要在业务上明确这一行为。测试用例覆盖定义域边界:
sqrt(0)、sqrt(-0.0)、ln(1)、ln(0)、log(1, x)、exp(709)、exp(710)。
下一节
继续学习:RANDOM / SETSEED