算术编码Arithmetic Coding-高质量代码实现详解
关于算术编码的具体讲解我不多细说,本文按照下述三个部分构成。
- 两个例子分别说明怎么用算数编码进行编码以及解码(来源:ARITHMETIC CODING FOR DATA COIUPRESSION);
- 接下来我会给出算术编码的压缩效果接近熵编码的证明方法(这一部分参考惠普公司的论文:Introduction to Arithmetic Coding - Theory and Practice);
- 最后我会详细说明一下算数编码的实现代码(代码来源:ACM87 ARITHMETIC CODING FOR DATA COIUPRESSION);
一, 直观上去认识算术编码
编码过程:将字符映射到 [0,1) 的区间的一个数
稍微说明一下,一开始将区间分为好几段,每一段表示一个字符。编码字符e的时候,就把原先区间表示e的那一段放大,对这个区间进行划分获得子区间,每个子区间也是代表一个字符。依次进行下去。编码结束的时候获得的那个区间就是我们要的,我们可以在这中间取个数就好了。
伪代码是这样的:
解码过程:将编码得到的数还原成字符串。
大概思路是这样的,就是每次看那个数处落在哪个子区间段,然后输出这个区间段所表示的字符。之后,调整区间以及这个数,递归知道输出所有编码字符为止。
二,证明算术编码的压缩效率
首先我们得确切知道我们到底编码出来的是什么,然后我们才能去进一步去证明。
经过上一步的直观认识,我们应该知道编码结束的时候我们获得一个最终的区间,然后取这个区间中的一个值来表示最终的编码。在实践中,我们是输出子区间上下界中的共同位。比如我们最终得到的区间是[0.1010011,0.1010000)那么共同位就是0.10100,当然喽,方便起见,我们就只保存10100就好了,而把小数点什么的去掉。
接下来就是证明了。
三,实现代码详解
着重讲一下编码过程中字符编码的实现,先看一下代码。功能在于完成一个字符的编码工作
1: static void bit_plus_follow(int); /* Routine that follows */
2: static code_value low, high; /* Ends of the current code region */
3: static long bits_to_follow; /* Number of opposite bits to output after */
4:
5:
6: void encode_symbol(int symbol,int cum_freq[])
7: {
8: long range; /* Size of the current code region */
9: range = (long)(high-low)+1;
10:
11: high = low + (range*cum_freq[symbol-1])/cum_freq[0]-1; /* Narrow the code region to that allotted to this */
12: low = low + (range*cum_freq[symbol])/cum_freq[0]; /* symbol. */
13:
14: for (;;)
15: { /* Loop to output bits. */
16: if (high<Half) {
17: bit_plus_follow(0); /* Output 0 if in low half. */
18: }
19: else if (low>=Half) { /* Output 1 if in high half.*/
20: bit_plus_follow(1);
21: low -= Half;
22: high -= Half; /* Subtract offset to top. */
23: }
24: else if (low>=First_qtr && high<Third_qtr) { /* Output an opposite bit later if in middle half. */
25: bits_to_follow += 1;
26: low -= First_qtr; /* Subtract offset to middle*/
27: high -= First_qtr;
28: }
29: else break; /* Otherwise exit loop. */
30: low = 2*low;
31: high = 2*high+1; /* Scale up code range. */
32: }
33: }
34:
35: static void bit_plus_follow(int bit)
36: {
37: output_bit(bit); /* Output the bit. */
38: while (bits_to_follow>0) {
39: output_bit(!bit); /* Output bits_to_follow */
40: bits_to_follow -= 1; /* opposite bits. Set */
41: } /* bits_to_follow to zero. */
42: }
详细说明:
6-12行就是简单地计算,根据当前编码字符找到我们需要的子区间。前面讲到伪代码的时候编码到这一步的时候就已经完成对该字符的编码,即将对下一字符编码了。可是,实际操作的时候,我们看到这样一次次运行,区间会越来越小,也就意味着要存的那个数位数越来越多,那么我们的计算机能不能存下呢?这是个很严重的问题。
解决的方法是这样的,我们注意到,要是区间的上下界中前面几个字符是一样的,那么以后编码的时候它们还是一样不变的.举个例子,要是编码区间为[0.1101,0.1111),那么后来再怎么编码,得到的区间还是[0.11~,0.11~)前面几个字符是一样的。那么我们是不是可以进行输出了呢,这样就可以避免溢出啦!16-23行代码就是执行这个的。
细心的同学就发现了还有24-28行代码的存在,他们是干嘛的呢?
我们举个,就是说区间卡在0.5这个地方,区间为[0.10~,0.01~)那么这种情况怎么处理?因为显然要是始终这样下去的话,16-23行代码是无能为力的。对此我们也是可以处理的。
此时的区间上下界应该是类似这样,前面相同的部分我们就不看了,默认已经由16-23行代码处理完毕。
我们先看这个例子,假设区间是[0.011,0.101),那么画图来看的话区间就是处于[3/8,6/8)之间,我们将原先区间的[2/8,6/8)放大一倍,那么此时原先的子区间就变成了[2/8,1),可以参见下图。
我们注意到放大后,如果编码下一个字符的时候,子区间存在于上半部分,也就是上图右边[4/8,1)之间,那么也就是上图左边[4/8,6/8)的位置,这个部分的编码为10,所以输出10。
通过这个例子我们就知道怎么处理了。
首先记录一下从[2/8,6/8)放大到区间[0,1)的次数bits_to_follow ,直到区间长度大于0.5为止。
然后开始编码下一个字符,如果区间存在于上半部,则输出10000,其中0的个数为bits_to_follow 个。
如果区间存在于下半部,则输出01111,其中1的个数为bits_to_follow 个。如果区间位于[2/8,6/8)则继续放大,bits_to_follow 也随之增加。
建议大家自己画图好好体会一下这段代码的妙处!
现在给出全部代码:很多小细节有待自己去研究,很微妙的。
#include<cstdio>
#include<stdlib.h>
using namespace::std; #define Code_value_bits 16 /* Number of bits in a code value */
typedef long code_value; /* Type of an arithmetic code value */ #define Top_value (((long)1<<Code_value_bits)-1) /* Largest code value */ #define First_qtr (Top_value/4+1) /* Point after first quarter */
#define Half (2*First_qtr) /* Point after first half */
#define Third_qtr (3*First_qtr) /* Point after third quarter */ #define No_of_chars 256 /* Number of character symbols */
#define EOF_symbol (No_of_chars+1) /* Index of EOF symbol */ #define No_of_symbols (No_of_chars+1) /* Total number of symbols */ /* TRANSLATION TABLES BETWEEN CHARACTERS AND SYMBOL INDEXES. */ int char_to_index[No_of_chars]; /* To index from character */
unsigned char index_to_char[No_of_symbols+]; /* To character from index */ /* CUMULATIVE FREQUENCY TABLE. */ #define Max_frequency 16383 /* Maximum allowed frequency count */
/* 2^14 - 1 */
int cum_freq[No_of_symbols+]; /* Cumulative symbol frequencies */ //固定频率表,为了方便起见
int freq[No_of_symbols+] = {
,
, , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , , , /* ! " # $ % & ' ( ) * + , - . / */
, , , , , , , , , , , , , , , , /* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */
, , , , , , , , , , , , , , , , /* @ A B C D E F G H I J K L M N O */
, , , , , , , , , , , , , , , , /* P Q R S T U V W X Y Z [ / ] ^ _ */
, , , , , , , , , , , , , , , , /* ' a b c d e f g h i j k l m n o */
, , , , , , , , , , , , , , , , /* p q r s t u v w x y z { | } ~ */
, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , , ,
, , , , , , , , , , , , , , , , }; //用来存储编码值,是编码解码过程的桥梁。大小暂定100,实际中可以修改
char code[];
static int code_index=;
static int decode_index=; //buffer为八位缓冲区,暂时存放编码制
static int buffer;
//buffer中还有几个比特没有用到,初始值为8
static int bits_to_go;
//超过了EOF的字符,也是垃圾
static int garbage_bits; //启用字符频率统计模型,也就是计算各个字符的频率分布区间
void start_model(){
int i;
for (i = ; i<No_of_chars; i++) {
//为了便于查找
char_to_index[i] = i+;
index_to_char[i+] = i;
} //累计频率cum_freq[i-1]=freq[i]+...+freq[257], cum_freq[257]=0;
cum_freq[No_of_symbols] = ;
for (i = No_of_symbols; i>; i--) {
cum_freq[i-] = cum_freq[i] + freq[i];
}
//这条语句是为了确保频率和的上线,这是后话,这里就注释掉
//if (cum_freq[0] > Max_frequency); /* Check counts within limit*/
} //初始化缓冲区,便于开始接受编码值
void start_outputing_bits()
{
buffer = ; //缓冲区一开始为空
bits_to_go = ;
} void output_bit(int bit)
{
//为了写代码方便,编码数据是从右到左进入缓冲区的。记住这一点!
buffer >>= ;
if (bit) buffer |= 0x80;
bits_to_go -= ;
//当缓冲区满了的时候,就输出存起来
if (bits_to_go==) {
code[code_index]=buffer;
code_index++; bits_to_go = ; //重新恢复为8
}
} void done_outputing_bits()
{
//编码最后的时候,当缓冲区没有满,则直接补充0
code[code_index]=buffer>>bits_to_go;
code_index++;
} static void bit_plus_follow(int); /* Routine that follows */
static code_value low, high; /* Ends of the current code region */
static long bits_to_follow; /* Number of opposite bits to output after */ void start_encoding()
{
for(int i=;i<;i++)code[i]='\0'; low = ; /* Full code range. */
high = Top_value;
bits_to_follow = ; /* No bits to follow */
} void encode_symbol(int symbol,int cum_freq[])
{
long range; /* Size of the current code region */
range = (long)(high-low)+; high = low + (range*cum_freq[symbol-])/cum_freq[]-; /* Narrow the code region to that allotted to this */
low = low + (range*cum_freq[symbol])/cum_freq[]; /* symbol. */ for (;;)
{ /* Loop to output bits. */
if (high<Half) {
bit_plus_follow(); /* Output 0 if in low half. */
}
else if (low>=Half) { /* Output 1 if in high half.*/
bit_plus_follow();
low -= Half;
high -= Half; /* Subtract offset to top. */
}
else if (low>=First_qtr && high<Third_qtr) { /* Output an opposite bit later if in middle half. */
bits_to_follow += ;
low -= First_qtr; /* Subtract offset to middle*/
high -= First_qtr;
}
else break; /* Otherwise exit loop. */
low = *low;
high = *high+; /* Scale up code range. */
}
} /* FINISH ENCODING THE STREAM. */ void done_encoding()
{
bits_to_follow += ; /* Output two bits that */
if (low<First_qtr) bit_plus_follow(); /* select the quarter that */
else bit_plus_follow(); /* the current code range */
} /* contains. */ static void bit_plus_follow(int bit)
{
output_bit(bit); /* Output the bit. */
while (bits_to_follow>) {
output_bit(!bit); /* Output bits_to_follow */
bits_to_follow -= ; /* opposite bits. Set */
} /* bits_to_follow to zero. */
} void encode(){
start_model(); /* Set up other modules. */
start_outputing_bits();
start_encoding();
for (;;) { /* Loop through characters. */
int ch;
int symbol;
ch = getchar(); /* Read the next character. */
//if (ch==EOF) break; /* Exit loop on end-of-file. */
//为了简单起见,这里就不用EOF为结尾了,直接使用回车符作为结尾。这不影响说明算法的原理
if(ch==)break;
symbol = char_to_index[ch]; /* Translate to an index. */
encode_symbol(symbol,cum_freq); /* Encode that symbol. */ }
//将EOF编码进去,作为终止符
encode_symbol(EOF_symbol,cum_freq);
done_encoding(); /* Send the last few bits. */
done_outputing_bits(); } //解码 static code_value value; /* Currently-seen code value */ void start_inputing_bits()
{
bits_to_go = ; /* Buffer starts out with */
garbage_bits = ; /* no bits in it. */
} int input_bit()
{
int t; if (bits_to_go==) {
buffer = code[decode_index];
decode_index++; // if (buffer==EOF) {
if(decode_index > code_index ){
garbage_bits += ; /* Return arbitrary bits*/
if (garbage_bits>Code_value_bits-) { /* after eof, but check */
fprintf(stderr,"Bad input file/n"); /* for too many such. */
// exit(-1);
}
}
bits_to_go = ;
}
//从左到右取出二进制位,因为存的时候是从右到左
t = buffer&; /* Return the next bit from */
buffer >>= ; /* the bottom of the byte. */
bits_to_go -= ;
return t;
} void start_decoding()
{
int i;
value = ; /* Input bits to fill the */
for (i = ; i<=Code_value_bits; i++) { /* code value. */
value = *value+input_bit();
} low = ; /* Full code range. */
high = Top_value;
} int decode_symbol(int cum_freq[])
{
long range; /* Size of current code region */
int cum; /* Cumulative frequency calculated */
int symbol; /* Symbol decoded */
range = (long)(high-low)+;
cum = (((long)(value-low)+)*cum_freq[]-)/range; /* Find cum freq for value. */ for (symbol = ; cum_freq[symbol]>cum; symbol++) ; /* Then find symbol. */
high = low + (range*cum_freq[symbol-])/cum_freq[]-; /* Narrow the code region *//* to that allotted to this */
low = low + (range*cum_freq[symbol])/cum_freq[]; for (;;) { /* Loop to get rid of bits. */
if (high<Half) {
/* nothing */ /* Expand low half. */
}
else if (low>=Half) { /* Expand high half. */
value -= Half;
low -= Half; /* Subtract offset to top. */
high -= Half;
}
else if (low>=First_qtr && high <Third_qtr) {
value -= First_qtr;
low -= First_qtr; /* Subtract offset to middle*/
high -= First_qtr;
}
else break; /* Otherwise exit loop. */
low = *low;
high = *high+; /* Scale up code range. */
value = *value+input_bit(); /* Move in next input blt. */
}
return symbol;
} void decode(){
start_model(); /* Set up other modules. */
start_inputing_bits();
start_decoding();
for (;;) { /* Loop through characters. */
int ch; int symbol;
symbol = decode_symbol(cum_freq); /* Decode next symbol. */
if (symbol==EOF_symbol) break; /* Exit loop if EOF symbol. */
ch = index_to_char[symbol]; /* Translate to a character.*/
putc(ch,stdout); /* Write that character. */
}
} int main()
{
encode();
decode();
system("pause");
return ;
}
算术编码Arithmetic Coding-高质量代码实现详解的更多相关文章
- KMP高质量代码实现详解
KMP算法 对于KMP算法我分为两个部分说明,第一部分是算法部分,介绍KMP算法的算法思想:第二部分是实现部分,介绍一种厉害的实现代码以及代码注释.当然了由于本文主要介绍怎么实现故而先分析实现,对KM ...
- 编写高质量代码--改善python程序的建议(八)
原文发表在我的博客主页,转载请注明出处! 建议四十一:一般情况下使用ElementTree解析XML python中解析XML文件最广为人知的两个模块是xml.dom.minidom和xml.sax, ...
- 编写高质量代码--改善python程序的建议(三)
原文发表在我的博客主页,转载请注明出处! 建议十三:警惕eval()的安全漏洞 相信经常处理文本数据的同学对eval()一定是欲罢不能,他的使用非常简单: eval("1+1==2" ...
- 编写高质量代码改善C#程序的157个建议——导航开篇
前言 由于最近工作重心的转移,原来和几个同事一起开发的项目也已经上线了,而新项目就是在现有的项目基础上进行优化延伸扩展.打个比方,现在已经上线的项目行政案件的Web管理网站(代码还没那么多相比较即将要 ...
- 编写高质量代码:改善Java程序的151个建议(第一章:JAVA开发中通用的方法和准则)
编写高质量代码:改善Java程序的151个建议(第一章:JAVA开发中通用的方法和准则) 目录 建议1: 不要在常量和变量中出现易混淆的字母 建议2: 莫让常量蜕变成变量 建议3: 三元操作符的类型务 ...
- 编写高质量代码改善C#程序的157个建议:第17个建议之多数情况下使用foreach进行循环遍历
今天是我看<编写高质量代码:改善C#程序的157个建议>第二遍的时候了,看完这本书的确是受益匪浅,学到了很多东西,也明白了很多道理. 里面的代码我每个都调试了一遍,有时候是有些出入的,可能 ...
- C# 《编写高质量代码改善建议》整理&笔记 --(一)基本语言篇
题记:这是自己的观后感,工作两年了,本来打算好好学习设计模式,或者作为客户端深入了解GPU编程的,但是突然发现还有这么一本书. <编写高质量代码改善建议>,感觉这正是自己需要的. 我是做 ...
- 编写高质量代码改善java程序的151个建议——[1-3]基础?亦是基础
原创地址: http://www.cnblogs.com/Alandre/ (泥沙砖瓦浆木匠),需要转载的,保留下! Thanks The reasonable man adapts himse ...
- 编写高质量代码:改善Java程序的151个建议 --[117~128]
编写高质量代码:改善Java程序的151个建议 --[117~128] Thread 不推荐覆写start方法 先看下Thread源码: public synchronized void start( ...
随机推荐
- Web Design:给实验室UI们的一堂课(上)
实验室的UI越来越水,设计什么的做的一塌糊涂,所以拖了很久,就想给他们讲一下设计或者说入门吧,上周末才倒出来时间. 这里放上PPT和讲稿吧,懒得去整理板式了. 主要讲了一下Web Design怎么做, ...
- Ruby判断文件是否存在
flag = FileTest::exist?("LochNessMonster") flag = FileTest::exists?("UFO") # exi ...
- spring事物的七种事物传播属性行为及五种隔离级别
首先,说说什么事务(Transaction). 事务,就是一组操作数据库的动作集合.事务是现代数据库理论中的核心概念之一.如果一组处理步骤或者全部发生或者一步也不执行,我们称该组处理步骤为一个事务.当 ...
- ORA-01017 invalid username/password;logon denied" (密码丢失解决方案)
1.先确认是否输错 用户名和密码 2.如果的确是丢失密码的话: 查看sqlnet.ora 如果是 SQLNET.AUTHENTICATION_SERVICES= (NONE) , 需更改为SQLNET ...
- Hadoop之Hive自定义函数的陷阱
A left join B, 这个B会连到A. 如<A1,B>, <A2,B>,在处理第一条记录的时候将B.clear(),则第二条记录的B是[]空的这是自定义UDF函数必须注 ...
- AngularJS 授权 + Node.js REST api
作者好屌啊,我不懂的他全都懂. Authentication with AngularJS and a Node.js REST api 几个月前,我开始觉得 AngularJS 好像好牛逼的样子,于 ...
- VBS数组函数学习实例分析
Array 函数 返回包含数组的Variant. Array(arglist) 参数:arglist是赋给包含在Variant中的数组元素的值的列表(用逗号分隔).如果没有指定此参数,则将会创建零长度 ...
- linux查看tomcat版本
进入tomcat bin目录下 然后执行 ./version.sh Server version: Apache Tomcat/6.0.26Server built: March 9 2010 1 ...
- storm集成kafka
kafkautil: import java.util.Properties; import kafka.javaapi.producer.Producer; import kafka.produce ...
- C#日志编写
在一个完整的信息系统里面,日志系统是一个非常重要的功能组成部分.它可以记录下系统所产生的所有行为,并按照某种规范表达出来.我们可以使用日志系统所记录的信息为系统进行排错,优化系统的性能,或者根据这些信 ...