🦏 第6章 内置函数
📄 一句话概括

九个常用的数学函数:abs、mod、round、truncate、sign、pi、rand、ceil、 floor。 两个重点:round(3.789, 2) 给 3.79 而 truncate 给 3.78(一个进位一个砍); ceil(-4.1) 给 -4、floor(-4.9) 给 -5—— 「向上」「向下」是往数轴哪边走,不是绝对值变大变小。 mod(-7, 3) 这里给 2,MySQL 给 -1——余数跟谁走,两边规定不同。

📝 本章和前面几章的关系

上一篇讲了函数的通用规矩,这一篇开始一个个过。 数学函数是最「稳」的一类——参数是数字,返回值是数字, 没有编码、没有时区、没有排序规则那些麻烦。 但有两个地方照样会翻车:小数取整的方向、负数取余的符号。

一、abs 与 sign:绝对值与正负号

abs(-3.5) 给 3.50,负变正、正的不动。 sign(x) 给 -1、0、1 三个值。 判断正负不用写 case when,一句话就够。 它还有个用处:把「差多少」变成「往哪边差」,做趋势图时很顺手。

二、mod:取余,但余数的符号两边不一样

mod(7, 3) 给 1, 两边一样。 但 mod(-7, 3) 这里给 2,MySQL 给 -1。 这是本章第二处「同名不同结果」。 差别在「余数跟谁走」:这里(SQLite)跟除数走,结果永远非负; MySQL 跟被除数走,结果与被除数同号。 什么时候会踩:拿余数当「桶号」分页、分片、哈希分组时,负数一出现就会分到负桶, MySQL 那边直接报错或越界。 要么保证被除数非负,要么写 abs() 兜一层。

三、round 与 truncate:一个进位,一个直接砍

round(3.789, 2) 给 3.79——看第三位是 9,进一位。 truncate(3.789, 2) 给 3.78——第三位直接扔掉。 0.5 这一档最能看出差别:round(2.5) 给 3.00,truncate(2.5, 0) 给 2.00。 负数同理,round(-2.5) 给 -3.00、truncate(-2.5, 0) 给 -2.00。 规律:truncate 永远让绝对值变小(往零的方向砍), round 不一定(往远离零的方向进)。 算钱用 round(该进就进),算「不超过某个上限」用 truncate。

四、ceil 与 floor:向上取整、向下取整

ceil(4.1) 给 5、floor(4.9) 给 4,这两个符合直觉。 负数就反直觉了:ceil(-4.1) 给 -4、floor(-4.9) 给 -5。 「向上」是往数轴右边走(数值变大),不是往绝对值大的方向走。 这一处最容易搞反。 记法:ceil 就是天花板,天花板永远在上面; floor 是地板,地板永远在下面——画在数轴上,负数也一样成立。

五、pi、pow 与 rand

pi() 给 3.141592653589793,它是少数不要参数的函数。 pow(2, 10) 给 1024.00。 rand() 在第 5 章讲过了:不给种子每次都变,给了种子就固定。 它也算数学函数,只是常用在「随机取几条」上。 还有几个没演示但值得知道:sqrt() 开方、exp() 自然指数、log() 对数。 用到的场景不多,查手册比记着强。

六、动手 Demo:从绝对值到取整,重点看两个方向

Demo 六段:abs/sign、mod、round/truncate、ceil/floor、pi/pow。 每段都在盯着符号与方向——这一篇的坑全在「负数是往哪边走」。

import _q46 as Q
c = Q.con()

c.execute("SELECT ABS(-3.5) AS a, SIGN(-9) AS s1, SIGN(0) AS s2, "
          "SIGN(9) AS s3")
# 取余:负数这一档两边不一样
c.execute("SELECT MOD(7, 3) AS a, MOD(-7, 3) AS b")
# round 进位、truncate 直接砍
c.execute("SELECT ROUND(3.789, 2) AS r2, TRUNCATE(3.789, 2) AS t2")
c.execute("SELECT ROUND(2.5) AS r, TRUNCATE(2.5, 0) AS t, "
          "ROUND(-2.5) AS rn, TRUNCATE(-2.5, 0) AS tn")
# 向上取整、向下取整
c.execute("SELECT CEIL(4.1) AS c1, CEIL(-4.1) AS c2, "
          "FLOOR(4.9) AS f1, FLOOR(-4.9) AS f2")
# pi 与 pow
c.execute("SELECT CAST(ROUND(PI(), 6) AS TEXT) AS pi6, POW(2, 10) AS p")

实跑输出:

--------------------------------------------------------------------
第二十四课:数学函数,重点在 round 与 truncate 的差别
--------------------------------------------------------------------

  abs 与 sign:一个取绝对值,一个取正负号
  SQL> SELECT ABS(-3.5) AS a, SIGN(-9) AS s1, SIGN(0) AS s2, SIGN(9) AS s3
  a        s1     s2     s3
  --------------------------
  3.50     -1     0      1
  (1 行)

  **`sign` 给 -1、0、1 三个值**,判断正负不用写 `case when`。
  **`abs` 就是绝对值**,负变正,正的不动。

  mod:取余,但余数的符号两边不一样
  SQL> SELECT MOD(7, 3) AS a, MOD(-7, 3) AS b
  a     b
  --------
  1     2
  (1 行)

  **`7 % 3` 给 1,两边一样。** **`-7` 取余 3 这里给 2,MySQL 给 -1。**
  **差别在「余数跟谁走」**:这里跟除数走(结果非负),
  **MySQL 跟被除数走(结果与被除数同号)。**
  **做分页、分组这类「余数当桶号」的场景,负数一出现就会分错桶。**

  round 与 truncate:一个进位,一个直接砍
  SQL> SELECT ROUND(3.789, 2) AS r2, TRUNCATE(3.789, 2) AS t2
  r2       t2
  --------------
  3.79     3.78
  (1 行)

  **3.789 保留两位:`round` 给 3.79(看第三位 9 进位),`truncate` 给 3.78(后面的直接扔掉)。**

  SQL> SELECT ROUND(2.5) AS r, TRUNCATE(2.5, 0) AS t, ROUND(-2.5) AS rn, TRUNCATE(-2.5, 0) AS tn
  r        t        rn        tn
  ----------------------------------
  3.00     2.00     -3.00     -2.00
  (1 行)

  **0.5 这一档:`round` 给 3.00 和 -3.00**——往远离零的方向进。
  **`truncate` 给 2.00 和 -2.00**——往零的方向砍。
  **所以 `truncate` 永远让绝对值变小,`round` 不一定。**
  **算钱用 `round`(该进就进),算「不超过某个值」用 `truncate`。**

  ceil 与 floor:向上取整、向下取整
  SQL> SELECT CEIL(4.1) AS c1, CEIL(-4.1) AS c2, FLOOR(4.9) AS f1, FLOOR(-4.9) AS f2
  c1     c2     f1     f2
  ------------------------
  5      -4     4      -5
  (1 行)

  **4.1 向上取整给 5,-4.1 向上取整给 -4**——**「向上」是往数轴右边走,不是往绝对值大的方向走。**
  **`floor(-4.9)` 给 -5,也是往数轴左边走。** **这一处符号最容易搞反。**

  pi、pow 与 rand
  SQL> SELECT CAST(ROUND(PI(), 6) AS TEXT) AS pi6, POW(2, 10) AS p
  pi6          p
  ---------------------
  3.141593     1024.00
  (1 行)

  **`pi()` 给 3.141592653589793,`pow(2, 10)` 给 1024.00。**
  **`rand()` 上一篇讲过:不给种子每次都变,给了种子就固定。**
  **`pi()` 不用给参数**,这在 SQL 里算少见的——**大部分函数至少要一个参数。**

第一段。 abs(-3.5) 给 3.50,sign 给 -1/0/1。 第二段取余。 mod(7,3) 给 1,mod(-7,3) 给 2——记住这一格,MySQL 那边是 -1。 第三段进位与砍。 3.79 与 3.78 差一位,就这一位决定账对不对。 第四段 0.5 那一档。 round 给 3.00/-3.00,truncate 给 2.00/-2.00。 第五段取整。 5、-4、4、-5——ceil(-4.1) 是 -4,往上走。 第六段。 pi 六位小数 3.141593,pow(2,10) 给 1024.00。

30 秒实验

  1. ROUND(2.5) 与 ROUND(3.5) 分别给什么?和 Python 的 round() 一致吗?
  2. TRUNCATE(-3.789, 1) 给多少?是 -3.7 还是 -3.8?
  3. CEIL(-0.5) 与 FLOOR(-0.5) 分别给什么?

常见误区

  1. 以为 truncate 和 round 差不多。 3.789 保留两位,一个 3.79 一个 3.78; 金额差一分,对账就永远差一分。
  2. 以为 ceil 是「绝对值变大」。 ceil(-4.1) 给 -4,绝对值变小了——它是往数轴右边走。
  3. 拿负数取余当桶号。 MySQL 给负余数,直接越界;两个引擎的桶号还会对不上。
自测三问(点开对答案) 1. `round` 与 `truncate` 在 3.789 上分别给什么? —— `round(3.789, 2)` 给 3.79(进位),`truncate(3.789, 2)` 给 3.78(砍掉)。 2. `ceil(-4.1)` 为什么给 -4 而不是 -5? —— `ceil` 是往数轴右边(数值变大)取整,-4 比 -4.1 大,所以是 -4。 3. `mod(-7, 3)` 在本沙箱和 MySQL 分别给什么? —— 本沙箱给 2(余数跟除数走),MySQL 给 -1(余数跟被除数走)。

数字过完了,下一篇轮到字符串——第一个坑就在 length 这个函数名上。

相关笔记