熵编码(四)-算术编码(二) 进阶篇
1. 前言
上篇文章熵编码(三)-算术编码(一)介绍了算术编码的基本流程和工作原理。本篇文章主要讲述算术编码区间缩放
2. 区间缩放背景
上篇文章编码过程使用的是无限精度的小数并且在编码结束的时候再转换成二进制。然而由于计算机能处理的精度有限,许多编码器使用的都是有限精度,从而导致编码区间随着编码会越来越小直至为0,因此可编码的字符数目受到编码器的精度限制
3. 区间缩放原理
算术编码区间范围是[0, 1)的小数,但是寄存器只能存储整数,因此寄存器存储的是区间的小数部分[0x00000000...0U, 0xFFFFFFFF....FU),区间上下限有无限个0/1.然而寄存器位数有限, 实际只能存储有限位数。
以32位寄存器为例,寄存器存储的是[0x00000000U, 0xFFFFFFFFU),但可以假象在寄存器精度之后还有无限个0/1的后缀。每当寄存器的区间下限L高位被移除时,低位补0,寄存器的区间上限H高位被移除时,低位补1。这种方式可以使用有限精度模拟无限精度。
3.1. 区间缩放基础
算术编码过程永远满足三个不等式:
- 区间下限Low永远单调递增
- 区间上限High永远单调递减
- 区间上限High > 区间下限Low
满足上述条件之后: 若High的最高位是0,此时Low的最高位也必定是0,那么在之后的编码过程中最高位不再变化;同理,若Low的最高位是1,此时High的最高位也必定是1,那么在之后的编码过程中最高位也不再变化。由于寄存器位数有限,为了最大化利用寄存器的位数编码,因此当区间上下限的最高位相同时,将最高位移出寄存器并输出,同时将区间上下限左移1位(通过上文可知下限补0,上限补1)。通过上述方法,我们可以充分利用寄存器的位数,也不会造成算法错误。
3.2. 区间缩放分析
上文中区间上下限最高位相同移除寄存器同时低位补0或1,属于区间缩放的两种情况。区间缩放情况分为一下三种(n是区间精度位数):
- 当Low最高位为1时,Low > Middle,由于High > Low永远成立,因此High的最高位也是1, 此时的小数区间是[0。5, 1),用二进制表示区间是0.1x。此时可以将High和low的最高位都移出并输出,即左移1位,同时低位补0或1:
Low = (low - 2n-1) << 1
High = ((High - 2n-1) << 1) | 1
- 当High最高位为0时,High < Middle,由于High > Low永远成立,因此Low的最高位也是0,此时的小数区间是[0, 0.5),用二进制表示区间是0.0x。此时可以将High和low的最高位都移出并输出,即左移1位,同时低位补0或1:
Low = Low << 1
High = (High << 1) | 1
- 当High最高两位是“10”,Low最高两位是“01”,此时的小数区间是[0.25, 0.75), 用二进制表示区间是0.01x或者0x10x。此时将High和Low的此高位移出,最高位不变,低位补0或1, 并且记录次高位移出次数cnt。在之后的编码过程中,遇到第一种区间缩放,输出一个0和cnt个1;遇到第二种区间缩放输出一个1和cnt个0
Low = (low - 2n-2) << 1
High = ((High - 2n-2) << 1) | 1
4. 区间缩放编码
以上篇文章编码示例为例,初始小数编码区间是[0, 1)。因为寄存器存储小数部分,因此寄存器编码区间是[0, 256)。下文编码过程描述的编码区间变化是寄存器编码区间变化,对应的小数编码区间变化不做描述,仅在最后输出编码结果的时候输出对应小数结果
举例:输入序列"aabbc", 符号概率如下表:
symbol | a | b | c |
---|---|---|---|
probability | 0.4 | 0.4 | 0.2 |
1.算术编码会根据概率分割初始编码区间[0,256), 其中区间的下限为表中前面的符号的累计概率
a:[0, 102), b:[102, 205), c:[205, 256)
字符序列第1个字符a,那么编码区间是[0, 102)。二进制区间[00000000b, 01100110b),区间缩放:输出一个“0”,最高位移出一位,缩放后二进制区间[00000000b, 11001101),编码区间是[0, 205)
2.按照字符概率重新划分[0, 205)编码区间
a:[0, 82), b:[82, 164), c:[164, 205)
字符序列第2个字符a,那么编码区间是[0, 82)。二进制区间[00000000b, 01010010b), 区间缩放:输出一个“0”,最高位移出一位,缩放后二进制区间[00000000b, 10100101b),编码区间是[0, 165)
3.按照字符概率重新划分[0, 165)编码区间
a:[0, 66), b:[66, 132), c:[132, 165)
字符序列第3个字符b,那么编码区间是[66, 132)。二进制区间[01000010b, 10000100b), 区间缩放:记录次高位次数cnt=1,次高位移出,缩放后二进制区间[000000100b, 10001001b),编码区间是[4, 137)
4.按照字符概率重新划分[4, 137)编码区间
a:[4, 57), b:[57, 110), c:[110, 137)
字符序列第4个字符b,那么编码区间是[57, 110)。二进制区间[00111001b, 01101110b), 区间缩放:输出一个“0”,次高位移出次数cnt=1个“1”,最高位移出一位,缩放后二进制区间[01110010b, 11011101b),编码区间是[114, 221)
5.按照字符概率重新划分[114, 221)编码区间
a:[114, 157), b:[157, 200), c:[200, 213)
字符序列第5个字符c,那么编码区间是[200, 213)。二进制区间[11001000b, 11010101b), 区间缩放:输出两个“1”,一个“0”,最高位移出三位,缩放后二进制区间[01000000b, 10101000b),编码区间是[104, 171)
缓存二进制0001110,最终的编码二进制低位补1得到结果是00011101,表示的小数是0.00011101(0.11328125)。可以看到算术编码使用区间缩放得到的编码结果和上篇文章使用无限精度小数得到的结果是一致的。说明编码方法是符合预期的,并且仅使用了8位bit实现了算术编码。使用区间缩放的算术解码流程就不叙述了,感兴趣的同学可以自己推导
总结:算术编码使用区间缩放可以使用有限精度位数实现算术编码,因此实际应用中算术编码算法都会使用区间缩放