Skip to content

SQRT / CBRT / EXP / LN / LOG ​

开方与对数是一组「互为逆运算」的函数家族。它们的难点不在语法,而在定义域与默认底数:sqrt(-1) 直接报错、ln(0) 报错、log(x) 单参数在 PostgreSQL 中默认以 10 为底(而不是多数编程语言的 e)、exp(1000) 会静默溢出成 infinity。本节把定义域、返回类型、精度边界讲清楚,并给出信息熵、对数增长率、分贝、欧氏距离四类实战算法。

前置知识

阅读本节前,建议先了解:CEIL / FLOOR / MOD / POWER

基础概念 ​

概念术语说明
平方根Square Rootsqrt(x),满足 y² = x 的非负数 y
立方根Cube Rootcbrt(x),满足 y³ = x 的实数 y
指数函数Exponentialexp(x) = eˣ
自然对数Natural Logarithmln(x) = logₑ(x)
常用对数Common Logarithmlog10(x) = log₁₀(x)
二进制对数Binary Logarithmlog₂(x),PostgreSQL 需用 log(2, x)
底数Base对数记号中的 a,满足 a^y = x
定义域Domain函数有定义的输入范围
溢出Overflow结果超出可表示范围,浮点下变为 infinity
精度损失Precision Loss浮点运算的舍入误差累积
数量级Order of Magnitude以 10 为底的指数部分
信息熵Entropy不确定性度量,单位比特

三个对数函数的底数对照(最容易记混的一张表):

调用底数等价写法典型语言差异
ln(x)e ≈ 2.718281828log(e, x)与 Python math.log(x) 一致
log(x)10log10(x)⚠️ Python / Java 的 log(x) 是自然对数
log(b, x)b—双参数形式,PostgreSQL 特有
log10(x)10log(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)。

sql
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 ≥ 0sqrt(-1) 报错
cbrt(x)任意实数—
exp(x)任意实数过大时返回 infinity
ln(x)x > 0ln(0)、ln(-1) 报错
log10(x) / log(b, x)x > 0 且 b > 0、b ≠ 1报错
power(a, b)底数为负时 b 必须是整数报错

一句话记忆:开方怕负数,对数怕零与负数,指数只怕溢出。

语法与用法 ​

sql
-- 开方
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)

最小可运行示例:

sql
-- 开方与对数的最小演示
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 为底)
text
 sq16 | cb27 | cb_neg8 | exp0 |        exp1         | ln1 | log1000 | log2_1024
------+------+---------+------+---------------------+-----+---------+-----------
    4 |    3 |      -2 |    1 | 2.718281828459045   |   0 |       3 |        10

准备本节贯穿使用的示例表:

sql
-- 内容平台:文章浏览分布,用于计算信息熵
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);
sql
-- 基础演示:对浏览量做对数分析
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;
text
        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。对负数会直接报错,这是它最常见的失败模式:

sql
-- 错误!ERROR: cannot take square root of a negative number
SELECT sqrt(-1);
sql
-- 合法用法
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
text
 zero |      sqrt2       | sqrt225 |  big   | small
------+------------------+---------+--------+-------
    0 | 1.4142135623731  |     1.5 | 100000 |  0.01

防御负数是必备习惯。数据可能因计算误差出现 -1e-16 这类「假负数」,需要用 greatest(x, 0) 夹取:

sql
-- 不推荐!遇到负的方差会整体报错
SELECT sqrt(variance_value) FROM metrics;
-- 正确:夹取到 0
SELECT sqrt(greatest(variance_value, 0)) FROM metrics;
sql
-- 浮点误差产生极小负数时的处理
SELECT
  -0.0000000000000001::double precision AS raw_value,
  sqrt(greatest(-0.0000000000000001::double precision, 0)) AS safe_sqrt;
text
      raw_value       | safe_sqrt
----------------------+-----------
 -1e-16               |         0

欧氏距离是 sqrt 的经典应用。计算两个商品在价格、销量、评分三维空间上的距离:

sql
-- 用 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;
text
 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 的「全域可定义」版本,接受任意实数:正数的立方根为正,负数的立方根为负。

sql
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
text
 a | b  | c | d | e  |          f
---+----+---+---+----+----------------------
 3 | -3 | 0 | 1 | 10 | 1.2599210498948732

为什么复杂立方根要用 cbrt 而不是 power(x, 1.0/3):

sql
-- 错误!负底数配非整数指数会报错
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:

sql
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
text
 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 的反函数。

sql
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(逆运算验证)
text
 a |         b         |        c        |         d          | e
---+-------------------+-----------------+--------------------+---
 1 | 2.718281828459045 | 7.38905609893065| 0.36787944117144233| 5

溢出会静默变成 infinity,不报错,这是最危险的行为:

sql
-- 溢出实测
SELECT
  exp(700)        AS near_limit,     -- 1.0142320547350045e+304
  exp(709)        AS still_ok,       -- 8.218407461554972e+307
  exp(710)        AS overflow;       -- Infinity(不报错!)
text
      near_limit       |      still_ok       | overflow
-----------------------+---------------------+----------
 1.0142320547350045e+304| 8.218407461554972e+307| Infinity

溢出会污染下游计算

exp(710) 返回 Infinity 而不是报错,会让后续所有计算变成 Infinity 或 NaN,且很难定位。对指数结果做范围校验:

sql
-- 安全写法:超过阈值时截断
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);
text
    safe_exp
----------------
 2.7183
 2.6881e+43
 infinity_guard

exp 与 ln 互逆,可用来实现任意底数的幂(当 power 精度不够时):

sql
-- 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
text
 by_power |      by_exp_ln      |  rounded
----------+---------------------+------------
     1024 | 1024.0000000000002  | 1024.000000

ln:自然对数 ​

ln(x) 以 e 为底。定义域是 x > 0,零和负数都报错:

sql
-- 错误!ERROR: cannot take logarithm of zero
SELECT ln(0);

-- 错误!ERROR: cannot take logarithm of a negative number
SELECT ln(-1);
sql
-- 合法用法
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
text
 a |   b   |        c        |         d          |        e
---+-------+-----------------+--------------------+-----------------
 0 | 1.000 | 2.302585092994046| -0.6931471805599453| 230.25850929940458

防御零与负数是必备:用 NULLIF 把非法值转成 NULL,或用 CASE 兜底:

sql
-- 安全写法:非法输入返回 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);
text
 v  |      safe_ln       |      case_ln
----+--------------------+--------------------
 10 | 2.302585092994046  | 2.302585092994046
  1 |                  0 |                  0
  0 |                    |  (NULL)
 -5 |                    |  (NULL)

log:两个重载,默认底数是 10 ​

log 有两个重载,这是本节最容易记错的地方:

sql
log(x)          -- 常用对数,底数为 10
log(b, x)       -- 以 b 为底 x 的对数
sql
-- 底数实测对照
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(注意参数顺序!)
text
 log_100 | log10_100 |      ln_100       |     log2_100     |    log100_2
---------+-----------+-------------------+------------------+------------------
       2 |         2 | 4.605170185988092 | 6.643856189774724| 0.1505149978319906

log(b, x) 的参数顺序是「底数在前」

log(2, 100) 是「以 2 为底 100 的对数」,不是「以 100 为底 2 的对数」。这个顺序与数学记号 log₂100 的阅读顺序一致,但与 Math.log(x) / Math.log(base) 的写法不同,迁移时务必核对。

没有 log2 函数,二进制对数必须用 log(2, x):

sql
-- 计算需要的比特数(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);
text
  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 precisionx ≥ 0负数报错
cbrt(x)—double precision全体实数不报错
exp(x)edouble precision全体实数溢出返回 Infinity
ln(x)edouble precisionx > 0零/负数报错
log(x)10numericx > 0零/负数报错
log10(x)10numericx > 0零/负数报错
log(b, x)bnumericx > 0、b > 0、b ≠ 1报错
sql
-- 返回类型实测
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
text
   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 | numeric

log 返回 numeric,ln 返回浮点

这看似奇怪的差异来自历史实现:log 家族在 PostgreSQL 中走 numeric 实现(精度更高),sqrt/ln/exp 走 C 库的 double precision 实现(性能更好)。需要高精度时优先用 log,需要性能时用 ln/exp。

信息熵:log 的经典应用 ​

信息熵公式为 H = -Σ pᵢ · log₂(pᵢ),衡量分布的不确定性。用 log(2, x) 实现:

sql
-- 计算每个分类的浏览量占比与信息熵贡献
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;
text
  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

熵计算里的两个坑

  1. p 不能为零。零概率项按数学定义是 0 · log(0) = 0,但 SQL 里 log(2, 0) 会报错,必须过滤 WHERE p > 0。
  2. 必须用 log(2, p) 而非 log(p)。若用 log(p)(10 为底),得到的是「以 10 为单位」的熵,与信息论定义的比特单位不符。

对数增长率与分贝 ​

对数增长率用于压缩巨大的数值跨度,让不同数量级的指标可在同一张图上对比:

sql
-- 浏览量跨越多个数量级时,用 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;
text
        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₁₀:

sql
-- 分贝换算:功率比与振幅比
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;
text
  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)) 实现任意底幂:

sql
-- 三种写法的精度对比
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
text
     by_power      |    by_exp_ln      |    rounded
-------------------+-------------------+----------------
 1.0717734625362931| 1.0717734625362931| 1.071773462536

实战示例 ​

场景一:用户相似度与欧氏距离 ​

在推荐系统中,用 sqrt 计算用户向量距离,找出最相似的用户:

sql
-- 用户行为向量:浏览、加购、下单、分享 四个维度(已归一化)
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;
text
 user_id | distance | similarity
---------+----------+------------
       3 | 0.070711 |   0.933967
       5 | 0.111803 |   0.899428
       4 | 1.135782 |   0.468203

场景二:音视频信号的动态范围统计 ​

用 log 把线性幅度压成 dB 刻度,并统计动态范围:

sql
-- 生成 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;
text
  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 求达到某水平所需时间:

sql
-- 缓存命中率按 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;
text
 target_ratio | tau_seconds | time_to_half_sec | time_to_half_min
--------------+-------------+------------------+------------------
          0.5 |        3600 |         -2494.94 |           -41.58
sql
-- 指数衰减曲线:每 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;
text
 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

注意事项 ​

  1. sqrt 对负数直接报错。计算方差、距离等可能因浮点误差产生 -1e-16 时,必须用 greatest(x, 0) 夹取。

  2. ln(0) 与 ln(-1) 都报错,不是返回 -Infinity 或 NULL。用 NULLIF(v, 0) 或 CASE WHEN v > 0 防御。

  3. log(x) 在 PostgreSQL 是 10 为底,不是 e 为底。自然对数用 ln(x)。这是跨语言迁移的头号坑。

  4. log(b, x) 的参数顺序是底数在前。log(2, 8) = 3,而 log(8, 2) = 1/3。

  5. 没有 log2(x) 函数。二进制对数写 log(2, x),返回类型是 numeric。

  6. exp 溢出静默返回 Infinity。exp(710) 不报错但结果无意义,必须做范围校验。

  7. power(-8, 1.0/3) 报错。负底数配非整数指数会导致复数结果,PostgreSQL 直接拒绝。用 cbrt 处理负数立方根。

  8. power(x, 0.5) 不等于 sqrt(x) 的精度。power(27, 1.0/3) 得到 2.9999999999999996,而 cbrt(27) 精确等于 3。

  9. sqrt/cbrt/exp/ln 返回 double precision,log 返回 numeric。混用时的隐式转换可能带来意外精度变化。

  10. 熵计算必须过滤零概率项。log(2, 0) 报错,需先 WHERE p > 0。

  11. ln 的结果可能为负(当 0 < x < 1 时)。做「增长率」计算前要确认取值区间,避免负增长被误判。

  12. 不要用对数处理金额。对数会引入浮点误差,金额计算应当用 numeric 的乘除幂,不要走 ln/exp。

  13. log(1, x) 与 log(b, x) 中 b = 1 会报错(除零),b 必须 > 0 且 ≠ 1。

最佳实践 ​

  1. 开方用专用函数:平方根 sqrt,立方根 cbrt。power(x, 0.5) / power(x, 1.0/3) 只在需要统一循环时才用。

  2. 对数的底数显式写出。即使默认是 10,也建议写 log(10, x) 或 log10(x),避免读者误读。跨语言对账时尤其重要。

  3. 把定义域防御固化到视图或函数里,避免每个查询都写一遍 CASE:

    sql
    CREATE VIEW v_articles_log AS
    SELECT
      id,
      title,
      views,
      CASE WHEN views > 0 THEN ln(views) END AS ln_views
    FROM articles;
  4. exp 前做范围校验,把 Infinity 拦在业务逻辑之外。

  5. 距离计算统一用 sqrt(power(...) + power(...)),避免手写 abs 近似。若维度很多(> 100),考虑用 cube 扩展或 vector 扩展。

  6. 排序用未舍入的原始表达式,展示才 round。舍入会破坏顺序精度。

  7. 分贝换算统一封装,功率比 10*log(r)、振幅比 20*log(a),把常量写进注释或函数,避免各处写错系数。

  8. 对数变换后的数据只用于展示与排序,不要回写到原始字段,避免不可逆的精度损失。

  9. numeric 精度要求高时优先 log(返回 numeric),性能敏感场景用 ln(返回浮点,走 C 库)。

  10. 跨语言对账时统一底数约定,并在接口文档里写明「本系统所有 log 默认为 10 为底」。

  11. 避免对负数取对数后静默得到 NULL。CASE 兜底时若返回 NULL,下游聚合会忽略该行,可能悄悄改变分母,需要在业务上明确这一行为。

  12. 测试用例覆盖定义域边界:sqrt(0)、sqrt(-0.0)、ln(1)、ln(0)、log(1, x)、exp(709)、exp(710)。

下一节

继续学习:RANDOM / SETSEED

参考链接 ​