01 / 本课学习路线
本课学习路线
阅读与推演约 112 分钟,练习约 80 分钟
02 / 学习目标与先修自测
学完本课你能做什么,以及开始前需要会什么
本课重点是按题目顺序处理定标、舍入与截断;先用下面的自测检查相关基础。
| 学完后能做的事 | 正文位置 | 检查方式 |
|---|---|---|
| 按顺序说出量化流程的五个环节 | 第 04 节 | 自查第 2 条 |
| 手写逢半远离零的实现,并说出它与 Python/Java 默认舍入的差别 | 第 04、08 节 | 自查第 3 条、代码自测 |
| 解释零点的作用与两级截断各自截断什么 | 第 06 节 | 自查第 4 条、练习 2 |
| 说明 K-Means 向量量化与 INT8 数值量化的区别 | 第 04 节补充学习 | 自查第 5 条、练习 5 |
| 解释误差 E 为什么用反量化值和原值比 | 第 05 节 | 自查第 6 条 |
| 通过 AI034 对称量化、AI035 非对称量化 | 第 05、06、08 节 | 自查第 1 条 |
先修自测:下面 5 题请先自己写答案,再展开对照。答不出的按括号里的位置补看再回来。本课假定你已完成模块 6 · 第 2 课「浮点精度与稳定 softmax」(取整与格式化在那里讲过)。
| 题号 | 题目 | 补看位置 |
|---|---|---|
| 自测 1 | 1 << 3 与 (1 << 3) - 1 各是多少?b=8 时 2^(b−1)−1 是多少? | 数字与运算符 |
| 自测 2 | round(0.5)、round(1.5)、round(2.5) 各是多少?规律是什么? | 浮点精度与稳定 softmax |
| 自测 3 | max(-8, min(7, 9)) 与 max(-8, min(7, -9)) 各是多少? | 常用内置函数 |
| 自测 4 | 把文本 "-0.5000" 读成「以 0.0001 为单位」的整数是多少?"1.0" 呢? | 字符串方法 |
| 自测 5 | (2 * 7 + 4) // (2 * 4) 与 (2 * 6 + 4) // (2 * 4) 各是多少?它们分别对应 7/4 与 6/4 的什么取整? | 数字与运算符 |
展开先修自测答案
自测 1:8 与 7;b=8 时 127。对称量化的整数域是 [−128, 127],非对称是 [0, 255]。
自测 2:0、2、2——恰好 .5 时向最近的偶数走(银行家舍入)。题目要的是 1、2、3。
自测 3:7 与 −8。这就是截断 clip(v, -8, 7) 的写法。
自测 4:−5000;10000。第 08 节的 parse_1e4 就做这件事:整数部分乘 10000 加上补足 4 位的小数部分。
自测 5:2 与 2——7/4 = 1.75 与 6/4 = 1.5 的逢半远离零取整。公式 (2·num + den) // (2·den) 对非负数就是「加 0.5 再向下取整」的整数写法。
03 / 概念与术语
位宽与整数域、缩放系数、零点、逢半远离零、截断、反量化与误差
分清缩放系数、零点、舍入、截断与误差,再跟踪它们在程序里的中间值。
| 术语 | 含义 | 代码里的位置 |
|---|---|---|
| 位宽 b 与整数域 | 对称:[−2^(b−1), 2^(b−1)−1],Q = 2^(b−1)−1;非对称:[0, 2^b−1],qmax = 2^b−1 | q_max = (1 << (b - 1)) - 1、q_max = (1 << b) - 1 |
| 缩放系数 s 与定标 s′ | 对称 s = M/Q,非对称 s = (mx−mn)/qmax;s′ 是 s 逢半远离零保留 6 位后的值,后续一律用 s′ | scale = round_half_away(m * 100, q_max) |
| 零点 zp | 非对称量化里「实数 0 对应的整数」:round(−mn/s′) 再截断到 [0, qmax] | zp = round_half_away(-lo * 100, scale) |
| 逢半远离零 | 恰好为 .5 时向远离 0 的方向舍入:0.5→1、−0.5→−1 | round_half_away(num, den) |
| 截断 clip | 越界值压到边界;AI035 对 zp 和 q 各截一次 | max(lo, min(hi, v)) |
| 反量化与误差 | 对称 d = q·s′,非对称 d = (q − zp)·s′;E = max|x − d| | err = max(err, abs(x * 100 - q * scale)) |
| 整数标度 | x 按 1e-4、s′ 按 1e-6 存成整数;x·100 与 s′ 同单位 | parse_1e4、fmt_1e6 |
04 / 五个环节
对称量化的五个环节,与 .5 的三种默认行为
n=5、b=4:x = [1.0000, −0.5000, 0.2500, −1.0000, 0.7500]。Q = 2³−1 = 7,M = 1。
对称量化全流程(逢半远离零)
s = M/Q = 1/7 = 0.142857142… → 6 位定标 s′ = 0.142857(比 1/7 略小) x/s′: 7.000007 -3.500004 1.750002 -7.000007 5.250005 round: 7 -4 2 -7 5 (−3.500004 已过 −3.5,远离零到 −4) 截断到 [−8, 7]:本例都在域内(但 s′ 下舍时最大元素会越过 Q,截断不能省——第 05 节的题库用例) 反量化 d = q×s′,误差 E = max|x−d| = |−0.5−(−0.571428)| = 0.071428
| 写法 | round(0.5) | round(−0.5) | 与题目「逢半远离零」 |
|---|---|---|---|
| Python round() | 0 | 0 | 不一致(银行家舍入) |
| Java Math.round() | 1 | 0 | 不一致(逢半向上) |
| 题目要求 | 1 | −1 | 必须自己实现 |
稳妥实现:符号函数(sign)乘以向下取整(floor),即 sign(x) × floor(|x| + 0.5)。更稳的做法是整数标度——x 按 1e4、s′ 按 1e6 转成整数后用整数运算完成舍入,浮点比较误差彻底消失(AI034/AI035 的数据规模允许全程 64 位整数)。第 07 节给出浮点版出错的具体用例。
非对称量化的两级截断是两次独立截断
全正数组时 −mn/s′ 是负数,zp 经截断后为 0——此时反量化并不能还原最小值本身(AI035 样例 2 的 E 高达 1.999925 正是这么来的——按定义算就是这个结果)。zp 的截断和 q 的截断作用于不同的中间量,应分别按题目定义执行。
补充学习(选学)整数标度对输出稳定性的作用约 4 分钟浮点在 1e-6 量级的比较误差怎么避开
量化题的输出要求固定保留 6 位小数:s′ 保留 6 位、误差保留 6 位,浮点实现会在 1e-6 量级出现「差一个最低位」的舍入差异,影响答案是否符合数值要求。把 x 乘 1e4、s′ 乘 1e6 全部转成整数后,舍入、截断、误差全部是精确整数运算——在 C++ 或 Java 中可用 64 位整数(long long / long)实现整数标度,减少中间浮点舍入差异;Python 的整数是任意精度,不会溢出。
补充学习(选学)向量量化与数值量化的压缩对象约 3 分钟K-Means 压的是「有多少种向量」,INT8 压的是「每个数占几位」
K-Means 向量量化:把 N 个向量用 k 个聚类中心代表,压的是「种类数」——存中心表 + 每个向量的编号。INT8 数值量化:每个数从 32 位压到 8 位,压的是「每个数的位宽」,逐元素线性映射、可逐元素还原(带误差)。前者是模块 6 · 第 3 课 的聚类,后者是本课的流程。两类量化的对象与还原方式不同,应分别理解和实现。
05 / AI034 手动计算
题面示例的每个元素,以及截断真的生效的用例
AI034「对称量化的缩放系数与误差」:第一行 n b,第二行 n 个实数(最多 4 位小数)。规则顺序:M=0 输出三行特殊值;s′ = M/Q 逢半远离零保留 6 位(下文记作 round6);q = clip(round(x/s′), −2^(b−1), Q);E = round6(max|x − q·s′|)。输出 s′、n 个整数、E。
| x | x / s′ | round(逢半远离零) | 截断到 [−8, 7] | d = q·s′ | |x − d| |
|---|---|---|---|---|---|
| 1.0000 | 7.000007 | 7 | 7 | 0.999999 | 0.000001 |
| −0.5000 | −3.500004 | −4 | −4 | −0.571428 | 0.071428 |
| 0.2500 | 1.750002 | 2 | 2 | 0.285714 | 0.035714 |
| −1.0000 | −7.000007 | −7 | −7 | −0.999999 | 0.000001 |
| 0.7500 | 5.250005 | 5 | 5 | 0.714285 | 0.035715 |
输出 0.142857 / 7 -4 2 -7 5 / 0.071428。误差用反量化值 d 和原值比,不是用整数 q 比——q 与 x 根本不在同一尺度。
| 输入 | 关键中间量 | 输出 |
|---|---|---|
2 2 / 1.0000 0.5000(b=2,Q=1) | s′ = 1.000000;0.5/1 = 0.5 恰好在中点,远离零取 1 | 1.000000 / 1 1 / 0.500000(银行家舍入会得 1 0) |
2 8 / 0.0009 -0.0004(b=8,Q=127) | s = 0.0009/127 = 0.00000708… → s′ = 0.000007(下舍);0.0009/0.000007 = 128.57 → 129 > 127 → 截到 127;−0.0004/0.000007 = −57.14 → −57 | 0.000007 / 127 -57 / 0.000011(不截断会得 129 -57) |
s′ 舍入到 6 位后比理论 s 小,最大元素除以 s′ 就可能超过 Q——这就是题面「范围截断必须生效」的来源。
06 / AI035 手动计算
零点与两级截断:两个题面示例
AI035「非对称量化与零点」:第一行 n b,第二行 n 个实数。规则顺序:qmax = 2^b−1;mx = mn 时 s′ = 1.000000,否则 s′ = round6((mx−mn)/qmax);zp = clip(round(−mn/s′), 0, qmax);q = clip(zp + round(x/s′), 0, qmax);d = (q − zp)·s′,E = round6(max|x − d|)。输出「s′ zp」、n 个整数、E。
| x | x / s′ | round | zp + round | 截断到 [0, 255] | d = (q − 87)·s′ | |x − d| |
|---|---|---|---|---|---|---|
| −1.2000 | −87.4317 | −87 | 0 | 0 | −1.194075 | 0.005925 |
| 0.0000 | 0 | 0 | 87 | 87 | 0 | 0 |
| 0.4500 | 32.7869 | 33 | 120 | 120 | 0.452925 | 0.002925 |
| 2.3000 | 167.5774 | 168 | 255 | 255 | 2.305800 | 0.005800 |
| −0.7500 | −54.6448 | −55 | 32 | 32 | −0.754875 | 0.004875 |
输出 0.013725 87 / 0 87 120 255 32 / 0.005925。零点 87 就是「实数 0 落在整数域的哪一格」——第 2 行 x=0 的量化值正是 87。
| x | x / s′ | zp + round | 截断 | d = q·s′ | |x − d| |
|---|---|---|---|---|---|
| 2.0000 | 169.9958 | 170 | 170 | 1.999950 | 0.000050 |
| 3.0000 | 254.9936 | 255 | 255 | 3.000075 | 0.000075 |
| 5.0000 | 424.9894 | 425 | 255 | 3.000075 | 1.999925 |
| 4.0000 | 339.9915 | 340 | 255 | 3.000075 | 0.999925 |
输出 0.011765 0 / 170 255 255 255 / 1.999925。第 1 行 d = 170 × 0.011765 = 1.999950,误差 0.000050;最大误差在 x=5:整数域被 zp=0 挤掉了下半段,5 与 4 都截到 255。两级截断各自独立:zp 截断改变的是「0 落在哪」,q 截断改变的是「越界的值落在哪」。
07 / 整数标度
用两组反例区分浮点表示误差与舍入规则错误
把 x 乘 10⁴、s′ 乘 10⁶ 存成整数,x·100 与 s′ 就在同一个单位(10⁻⁶)上,「x/s′ 取整」变成「两个整数相除取整」,「误差」变成两个整数相减——没有一步涉及二进制浮点。
| 输入 | 浮点版 | 整数版(题库期望) | 分歧原因 |
|---|---|---|---|
4 8 / -0.0050 0.0053 0.0000 -0.0049(AI035) | 第二行 0 255 125 3 | 第二行 0 255 125 2 | −0.0049 / 0.00004 在二进制浮点里是 −122.49999…,取整得 −122;精确值是 −122.5,远离零得 −123,加 zp=125 得 2 |
3 8 / -2.5000 -2.5000 -2.5000(AI035,用内置 round) | 1.000000 2 | 1.000000 3 | zp = round(2.5):银行家舍入得 2,逢半远离零得 3 |
第一组中,即使舍入函数方向正确,未经误差处理的 float 除法也会把中点移到另一侧。第二组的 2.5 可精确表示,错在内置 round 采用银行家舍入,不是 float 表示误差造成的。两题都允许精确中点;本课用整数标度统一处理,避免中间二进制浮点误差,同时显式实现逢半远离零。
| 写法 | 输入 2 2 / -0.0050 0.0250(s′=0.01,zp=1) | 原因 |
|---|---|---|
| 先取整再加零点(题面):zp + round(x/s′) | 1 + round(−0.5) = 1 − 1 = 0 | −0.5 远离零到 −1 |
| 先加零点再取整:round(x/s′ + zp) | round(0.5) = 1 | 0.5 远离零到 1——两种写法在负的 .5 上差 1 |
正确输出 0.010000 1 / 0 3 / 0.005000,写反得 1 3。题面公式的括号位置就是运算顺序,不能合并。
08 / 从取整函数到程序
参考实现与两份完整程序,每一步落在哪几行
两份程序共用三个工具函数:round_half_away(整数逢半远离零)、parse_1e4(十进制文本读成 10⁻⁴ 单位整数)、fmt_1e6(10⁻⁶ 单位整数打印成 6 位小数)。
| 步骤 | AI034 | AI035 |
|---|---|---|
| 读入 | parse_1e4 逐个转整数 | 同左 |
| 整数域 | q_max = (1 << (b - 1)) - 1、q_min = -(1 << (b - 1)) | q_max = (1 << b) - 1,下界 0 |
| 缩放系数 | M=0 特判;scale = round_half_away(m * 100, q_max) | hi == lo 时 1000000;否则 round_half_away((hi - lo) * 100, q_max) |
| 零点 | 无 | zp = round_half_away(-lo * 100, scale) 再截断 |
| 量化 | round_half_away(x * 100, scale) 再截断 | zp + round_half_away(x * 100, scale) 再截断 |
| 误差 | abs(x * 100 - q * scale) | abs(x * 100 - (q - zp) * scale) |
| 输出 | fmt_1e6 | fmt_1e6 + zp |
展开参考实现:逢半远离零取整(浮点版 rhaz 与整数版 round_half_away,自带断言)
rhaz 与 round_half_away 的参考实现(自带断言)
Pythonimport math
def rhaz(x):
# 逢半远离零:先对绝对值加 0.5 向下取整,再还原符号
return int(math.copysign(math.floor(abs(x) + 0.5), x))
def round_half_away(num, den):
# 整数精确版:num / den 逢半远离零取整(den > 0);量化题里 num、den 都是「乘过 10 的幂」的整数
if num >= 0:
return (2 * num + den) // (2 * den)
return -((-2 * num + den) // (2 * den))
assert rhaz(0.5) == 1 and rhaz(-0.5) == -1
assert rhaz(2.5) == 3 and rhaz(-2.5) == -3
assert rhaz(1.4) == 1 and rhaz(-1.4) == -1
# 对照:Python 内置 round 是银行家舍入
assert round(0.5) == 0 and round(2.5) == 2
# 整数版与浮点版在普通输入上一致
assert round_half_away(5, 10) == 1 and round_half_away(-5, 10) == -1
assert round_half_away(25, 10) == 3 and round_half_away(-25, 10) == -3
# 浮点版的陷阱:-0.0049 / 0.00004 在二进制浮点里是 -122.49999…,取整得 -122;整数版 -49 / 0.4 → -490 / 4 精确落在 -122.5,得 -123
assert rhaz(-0.0049 / 0.00004) == -122
assert round_half_away(-490, 4) == -123最后两条断言就是第 07 节的分歧用例:同一个 −122.5,浮点版得 −122、整数版得 −123。
展开完整参考程序 1:AI034 对称量化的缩放系数与误差
完整程序:AI034(标准输入 → 标准输出)
Pythonimport sys
def round_half_away(num, den):
"""整数精确版:num / den 逢半远离零取整(den > 0)。"""
if num >= 0:
return (2 * num + den) // (2 * den)
return -((-2 * num + den) // (2 * den))
def parse_1e4(token):
"""把最多 4 位小数的十进制文本读成「以 0.0001 为单位」的整数,避免二进制浮点误差。"""
neg = token.startswith("-")
if neg:
token = token[1:]
whole, _, frac = token.partition(".")
value = int(whole or "0") * 10000 + int((frac + "0000")[:4])
return -value if neg else value
def fmt_1e6(value):
"""把「以 0.000001 为单位」的整数打印成 6 位小数。"""
sign = "-" if value < 0 else ""
value = abs(value)
return f"{sign}{value // 1000000}.{value % 1000000:06d}"
def main():
data = sys.stdin.read().split()
if not data:
return
n, b = int(data[0]), int(data[1])
xs = [parse_1e4(t) for t in data[2:2 + n]] # 单位 1e-4
q_max = (1 << (b - 1)) - 1
q_min = -(1 << (b - 1))
m = max(abs(x) for x in xs)
if m == 0: # 规则 1:全零
print("0.000000")
print(" ".join(["0"] * n))
print("0.000000")
return
scale = round_half_away(m * 100, q_max) # 规则 2:s' = round6(M / Q),单位 1e-6
qs = []
err = 0
for x in xs:
q = round_half_away(x * 100, scale) # 规则 3:round(x / s'),两边都换成 1e-6 单位
q = max(q_min, min(q_max, q)) # 范围截断
qs.append(q)
err = max(err, abs(x * 100 - q * scale)) # 规则 4:|x − q·s'|,单位 1e-6
print(fmt_1e6(scale))
print(" ".join(map(str, qs)))
print(fmt_1e6(err))
main()x * 100 把 10⁻⁴ 单位换成 10⁻⁶ 单位,与 scale 同单位后才能相除、相减。求极值与逐元素量化各扫描一次,总量 O(n)。用题面示例 1 核对:0.142857 / 7 -4 2 -7 5 / 0.071428。
展开完整参考程序 2:AI035 非对称量化与零点
完整程序:AI035(标准输入 → 标准输出)
Pythonimport sys
def round_half_away(num, den):
"""整数精确版:num / den 逢半远离零取整(den > 0)。"""
if num >= 0:
return (2 * num + den) // (2 * den)
return -((-2 * num + den) // (2 * den))
def parse_1e4(token):
"""把最多 4 位小数的十进制文本读成「以 0.0001 为单位」的整数,避免二进制浮点误差。"""
neg = token.startswith("-")
if neg:
token = token[1:]
whole, _, frac = token.partition(".")
value = int(whole or "0") * 10000 + int((frac + "0000")[:4])
return -value if neg else value
def fmt_1e6(value):
"""把「以 0.000001 为单位」的整数打印成 6 位小数。"""
sign = "-" if value < 0 else ""
value = abs(value)
return f"{sign}{value // 1000000}.{value % 1000000:06d}"
def main():
data = sys.stdin.read().split()
if not data:
return
n, b = int(data[0]), int(data[1])
xs = [parse_1e4(t) for t in data[2:2 + n]] # 单位 1e-4
q_max = (1 << b) - 1
lo, hi = min(xs), max(xs)
if hi == lo: # 规则 2:全相同时 s' = 1.000000
scale = 1000000
else:
scale = round_half_away((hi - lo) * 100, q_max) # s' = round6((mx − mn) / qmax),单位 1e-6
zp = round_half_away(-lo * 100, scale) # 规则 3:零点 round(−mn / s')
zp = max(0, min(q_max, zp))
qs = []
err = 0
for x in xs:
q = zp + round_half_away(x * 100, scale) # 规则 4:zp + round(x / s')
q = max(0, min(q_max, q))
qs.append(q)
err = max(err, abs(x * 100 - (q - zp) * scale)) # 规则 5:|x − (q − zp)·s'|
print(f"{fmt_1e6(scale)} {zp}")
print(" ".join(map(str, qs)))
print(fmt_1e6(err))
main()与 AI034 的差别:缩放系数改用 (mx−mn)/qmax,mx=mn 时取 1.000000;整数域下界 0;多一个零点;反量化减去零点。用题面示例 1 核对:0.013725 87 / 0 87 120 255 32 / 0.005925。
09 / 边界、反例与复杂度
错误做法在题库用例上各输出什么
下面每一行都是一个具体的错误程序,给出输入、错误输出与正确输出;把参考程序改成对应写法就能复现。
| 错误做法 | 输入 | 错误输出 | 正确输出 | 判题结果 |
|---|---|---|---|---|
AI034 用内置 round(银行家舍入) | 2 2 / 1.0000 0.5000 | 第二行 1 0 | 第二行 1 1 | 答案错误(WA) |
| AI034 不截断 | 2 8 / 0.0009 -0.0004 | 0.000007 / 129 -57 / 0.000003 | 0.000007 / 127 -57 / 0.000011 | 答案错误(WA) |
| AI034 Q 写成 2^(b−1) | 题面示例 1 | 0.125000 / 8 -4 2 -8 6 / 0.000000 | 0.142857 / 7 -4 2 -7 5 / 0.071428 | 答案错误(WA) |
| AI035 零点不截断 | 题面示例 2 | 0.011765 -170 / 0 85 255 170 / 0.000125 | 0.011765 0 / 170 255 255 255 / 1.999925 | 答案错误(WA) |
| AI035 先加零点再取整 | 2 2 / -0.0050 0.0250 | 第二行 1 3 | 第二行 0 3 | 答案错误(WA) |
| AI035 反量化漏减零点 | 题面示例 1 | 第三行 1.200000 | 第三行 0.005925 | 答案错误(WA) |
| AI035 全相同时不设 s′=1 | 3 8 / -2.5000 -2.5000 -2.5000 | 除以零的运行错误 | 1.000000 3 / 0 0 0 / 0.500000 | 运行错误(RE) |
| AI035 浮点直接取整(正确的舍入函数) | 4 8 / -0.0050 0.0053 0.0000 -0.0049 | 第二行 0 255 125 3 | 第二行 0 255 125 2 | 答案错误(WA) |
把参考程序改成对应写法运行,就能得到表里的错误输出。浮点版在多数输入上与整数版一致,只在 x/s′ 恰好落在 .5 附近的输入上分歧——「碰巧过了样例」不是整数标度可以省的理由。
| 做法 | 时间 | 本课规模下 |
|---|---|---|
| 两趟扫描(求极值 + 逐元素量化) | O(n) | 每个元素只做常数次量化与误差计算 |
| 每个元素重算一次最大值 | O(n²) | 10¹⁰,必然超时 |
10 / 渐进练习与参考答案
跟做 → 改一个条件 → 独立实现 → 迁移
每题先在纸上或文件里做完,再展开答案。
练习 1(跟做):按第 05 节表格的格式,把题库用例 4 2 / 0.8000 -0.8000 0.3000 -0.2000(b=2)算完。
展开练习 1 答案
Q = 1,M = 0.8,s′ = 0.800000。x/s′ = 1、−1、0.375、−0.25 → 取整 1、−1、0、0,都在 [−2, 1] 内。d = 0.8、−0.8、0、0,误差 0、0、0.3、0.2 → E = 0.300000。输出 0.800000 / 1 -1 0 0 / 0.300000。
练习 2(改一个条件):AI035 示例 1 的 b 改成 4(qmax=15),s′、zp 与 x=2.3 的量化值各是多少?
展开练习 2 答案
s = 3.5/15 = 0.23333… → s′ = 0.233333;zp = round(1.2/0.233333) = round(5.14) = 5;x=2.3:2.3/0.233333 = 9.857 → 10,5 + 10 = 15,恰在上界不截。位宽从 8 降到 4,误差上限从 0.0069 升到 0.117——整数域越小,每格代表的实数区间越宽。
练习 3(改一个条件):AI035 的输入全是负数 4 8 / -10.0000 -9.5000 -8.0000 -9.9900,零点会截到哪一端?最大误差为什么很大?
展开练习 3 答案
s′ = round6(2/255) = 0.007843;zp = round(10/0.007843) = round(1275.0) = 1275 → 截到 255。q = 255 + round(x/s′):x = −10 → 255 − 1275 < 0 → 0;四个值全截到 0。d = (0 − 255) × 0.007843 = −2.000000(精确 −1.999965),x=−10 的误差 8.000035。输出 0.007843 255 / 0 0 0 0 / 8.000035(题库用例)。全负数组把零点推到上界,整数域的 255 格全部落在 [−2, 0] 上,−10 到 −8 一格也分不到。
练习 4(独立实现):完成「必做任务 1」的 rhaz,再写整数版 round_half_away(num, den),用第 07 节的分歧用例验证:浮点版算 rhaz(-0.0049 / 0.00004) 得 −122,整数版算 round_half_away(-490, 4) 得 −123。注意不能用 rhaz(-490 / 4) 代替浮点版:−122.5 可以精确表示,两版都得 −123,看不出分歧。
展开练习 4 答案
rhaz 与 round_half_away 的参考实现(自带断言)
Pythonimport math
def rhaz(x):
# 逢半远离零:先对绝对值加 0.5 向下取整,再还原符号
return int(math.copysign(math.floor(abs(x) + 0.5), x))
def round_half_away(num, den):
# 整数精确版:num / den 逢半远离零取整(den > 0);量化题里 num、den 都是「乘过 10 的幂」的整数
if num >= 0:
return (2 * num + den) // (2 * den)
return -((-2 * num + den) // (2 * den))
assert rhaz(0.5) == 1 and rhaz(-0.5) == -1
assert rhaz(2.5) == 3 and rhaz(-2.5) == -3
assert rhaz(1.4) == 1 and rhaz(-1.4) == -1
# 对照:Python 内置 round 是银行家舍入
assert round(0.5) == 0 and round(2.5) == 2
# 整数版与浮点版在普通输入上一致
assert round_half_away(5, 10) == 1 and round_half_away(-5, 10) == -1
assert round_half_away(25, 10) == 3 and round_half_away(-25, 10) == -3
# 浮点版的陷阱:-0.0049 / 0.00004 在二进制浮点里是 -122.49999…,取整得 -122;整数版 -49 / 0.4 → -490 / 4 精确落在 -122.5,得 -123
assert rhaz(-0.0049 / 0.00004) == -122
assert round_half_away(-490, 4) == -123见第 08 节展开区(同一份代码)。
练习 5(迁移):模块 6 · 第 3 课 的 K-Means 也叫「向量量化」,它和本课的 INT8 量化各压缩了什么、各怎么还原?
展开练习 5 答案
K-Means 向量量化压的是「种类数」:N 个向量用 k 个中心代表,存中心表 + 每个向量的编号,还原是查表取中心(整个向量被替换)。INT8 数值量化压的是「每个数的位宽」:逐元素线性映射 q = round(x/s′)(+ zp),还原是 d = q·s′(− zp·s′),逐元素带误差还原。第 04 节补充学习「向量量化与数值量化的压缩对象」。
11 / 读题要求与复习自评
两道题的要求对照,以及完成本课之后怎么复习
提交前把下表过一遍;题目页的题面与样例是最终依据。
| 项目 | AI034 对称 | AI035 非对称 |
|---|---|---|
| 整数域 | [−2^(b−1), 2^(b−1)−1],Q = 2^(b−1)−1 | [0, 2^b−1],qmax = 2^b−1 |
| 缩放系数 | s′ = round6(max|x| / Q) | s′ = round6((mx − mn)/qmax);mx = mn 时 1.000000 |
| 特殊分支 | M = 0:输出 0.000000、n 个 0、0.000000 | 无(s′ 恒为正) |
| 零点 | 无 | zp = clip(round(−mn/s′), 0, qmax) |
| 量化 | clip(round(x/s′), −2^(b−1), Q) | clip(zp + round(x/s′), 0, qmax) |
| 误差 | max|x − q·s′| | max|x − (q − zp)·s′| |
| 输出 | s′ / n 个整数 / E,6 位小数 | 「s′ zp」/ n 个整数 / E |
参考程序:需要对照解法时,展开第 08 节的两份完整程序。复习与自评:「学习完成检查」六条是自评,勾选不改变题目的通过(AC)状态。本课记为完成的条件:必做题 AI034、AI035 都通过,并勾选全部六条学习完成检查;复习题不计入完成状态。复习时用三个问题自测:① 不看正文,按顺序写出五个环节和每步的舍入方式;② 不看表格,重算 AI034 示例 x=−0.5 的量化值与误差;③ 说出为什么 −0.0049/0.00004 要用整数算、先加零点再取整错在哪。答不出哪一条,就回到对应的节重读,再做第 10 节对应的练习。
12 / 练习
按顺序完成本课的任务
必做题已通过 0/2 道
rhaz)——本课两道题的一切误差都经过这个函数。AI034 全部正确后再做 AI035,再加入零点计算、两级截断和非对称整数域处理。参考实现与完整程序在第 08 节的展开区里:先自己写完并提交一次,再展开对照。逢半远离零取整函数(rhaz)
必做任务 1自主练习练习重点:0.5→1、−0.5→−1;与语言默认舍入区分;预计用时:10 分钟
完成标准:能说出 Python round 和题目规则在哪些输入上不一样
需要时查看提示
sign(x) × floor(|x| + 0.5)。最后一条断言对照 Python 默认行为——round(0.5)==0、round(2.5)==2,银行家舍入在 .5 处向偶数走。参考实现(含整数版)在第 08 节。
自测代码(复制到你的代码文件中运行,检查输出是否一致)
def rhaz(x):
# 待完成:逢半远离零取整(0.5 -> 1,-0.5 -> -1)
...
assert rhaz(0.5) == 1 and rhaz(-0.5) == -1
assert rhaz(2.5) == 3 and rhaz(-2.5) == -3
assert rhaz(1.4) == 1 and rhaz(-1.4) == -1
# 对照:Python 内置 round 是银行家舍入
assert round(0.5) == 0 and round(2.5) == 2AI034 · 对称量化的缩放系数与误差
必做任务 2练习重点:M→s→s′ 定标→舍入→截断→反量化误差,五步按题目顺序;预计用时:35 分钟
完成标准:能解释为什么 s′ 定标后截断可能真的生效
需要时查看提示
全部『四舍五入』都是逢半远离零,用自己写的 rhaz;s 先定标成 6 位的 s′ 再量化(用理论 s 会整体偏差);负端边界是 −2^(b−1) 不是 −Q;M=0 时按题目要求输出三行特殊值。第 05 节有示例的逐元素表与截断生效的用例。
AI035 · 非对称量化与零点
必做任务 3练习重点:zp 先算再截断;计算 q 后,再将 q 独立截断到 [0,qmax];误差用反量化值比;预计用时:35 分钟
完成标准:能解释全正数组时 zp 为什么被截断为 0、误差为什么会变大
需要时查看提示
zp=clip(rhaz(−mn/s′), 0, qmax) 是第一级;q=clip(zp + rhaz(x/s′), 0, qmax) 是第二级——两级独立。mx==mn 时 s′ 按题目规定取 1.000000;误差 E 用反量化值和原值比,不是量化整数比。第 06 节有两个示例的逐元素表,第 07 节说明为什么要整数标度。
提交结果
提交结果说明与处理方法
- WA
答案错误
三查:round 是不是逢半远离零(.5 边界用例检查这一规则)、s′ 定标顺序、zp 与 q 的两级截断是否都做了。第 09 节的表给出了每种错误在题库用例上的输出
- PE
格式错误
s′ 和 E 都保留 6 位小数;AI035 第一行是 s′ 和 zp 两个值空格分隔
- RE
运行错误
n 到 10⁵,读入用一次性整体读取;M=0/mx==mn 的特殊分支不要漏
- TLE
超时
两题都是 O(n) 两趟扫描;超时查是不是在循环里重复计算最大值
- AC
通过
说明对称与非对称的整数域各是什么,以及为什么对称方案的整数域本身不对称
13 / 学习完成检查
本课学习完成检查
完成本课需要:必做题全部通过,并勾选本课的全部学习完成检查;进阶练习、基础加练与复习题单独统计,不影响完成状态。登录后,勾选记录会保存到账号,并更新课程总览的完成状态。