C#,数值计算——算术编码压缩技术与方法(Compression by Arithmetic Coding)源代码

算术编码的数据压缩

算术编码是无损和有损数据压缩算法中常用的一种算法。

这是一种熵编码技术,其中常见符号比罕见符号用更少的比特进行编码。与诸如霍夫曼编码之类的众所周知的技术相比,它具有一些优势。本文将详细描述CACM87算术编码的实现,让您很好地了解实现它所需的所有细节。

从历史的角度来看,这篇文章是我20多年前写的一篇关于算术编码的数据压缩文章的更新。这篇文章发表在多布博士期刊的印刷版上,这意味着为了避免过多的页数,我们进行了大量的编辑。特别是,多布博士的文章结合了两个主题:算术编码的描述,以及使用PPM(部分匹配预测)进行压缩的讨论。

因为这篇新文章将在网上发布,空间考虑不再是一个重要因素,我希望这能让我公正地对待算术编码的细节。PPM本身就是一个有价值的话题,将在后面的文章中讨论。我希望这项新的努力,虽然冗长得令人恼火,但将是对我在1991年想做的主题的彻底解释。

我认为理解算术编码的最好方法是将其分为两部分,我将在本文中使用这个想法。首先,我将描述算术编码是如何工作的,使用标准C++数据类型实现的常规浮点算术。这允许实现一个完全可以理解但有点不切实际的实现。换句话说,它是有效的,但它只能用于编码非常短的消息。

文章的第二部分将描述一种实现,在该实现中,我们切换到对无界二进制数进行特殊类型的数学运算。这本身就是一个有点令人难以置信的话题,所以如果你已经理解了算术编码,它会有所帮助——你不必为同时学习两件事而烦恼。

最后,我将介绍用现代C++编写的工作示例代码。它不一定是世界上优化程度最高的代码,但它是可移植的,很容易添加到现有项目中。它应该非常适合学习和试验这种编码技术。

基本原理

关于算术编码,首先要了解的是它产生了什么。算术编码采用由符号(几乎总是八位字符)组成的消息(通常是一个文件),并将其转换为大于或等于零且小于1的浮点数。这个浮点数字可能很长——实际上,整个输出文件都是一个长数字——这意味着它不是传统编程语言中习惯使用的普通数据类型。我的算法实现必须从头开始,一点一点地创建这个浮点数,同样地,一点地读入并解码它。

这个编码过程是逐步完成的。当文件中的每个字符都被编码时,一些比特将被添加到编码的消息中,因此随着算法的进行,它会随着时间的推移而建立起来。

关于算术编码,需要理解的第二件事是,它依赖于一个模型来表征它正在处理的符号。该模型的工作是告诉编码器给定消息中字符的概率是多少。如果模型给出了消息中字符的准确概率,那么它们将被编码为非常接近最优。如果模型歪曲了符号的概率,那么编码器实际上可能会扩展消息,而不是压缩它!

using System;

namespace Legalsoft.Truffer
{
    /// <summary>
    /// compression by arithmetic coding
    /// </summary>
    public class Arithcode
    {
        private int NWK { get; } = 20;
        private int nch { get; set; }
        private int nrad { get; set; }
        private int ncum { get; set; }
        private int jdif { get; set; }
        private int nc { get; set; }
        private int minint { get; set; }
        private int[] ilob { get; set; }
        private int[] iupb { get; set; }
        private int[] ncumfq { get; set; }

        public Arithcode(int[] nfreq, int nnch, int nnrad)
        {
            this.nch = nnch;
            this.nrad = nnrad;
            this.ilob = new int[NWK];
            this.iupb = new int[NWK];
            this.ncumfq = new int[nch + 2];

            if (nrad > 256)
            {
                throw new Exception("output radix must be <= 256 in Arithcode");
            }

            minint = (int)(int.MaxValue / nrad);
            ncumfq[0] = 0;
            for (int j = 1; j <= nch; j++)
            {
                ncumfq[j] = ncumfq[j - 1] + Math.Max(nfreq[j - 1], 1);
            }
            ncum = ncumfq[nch + 1] = ncumfq[nch] + 1;
        }

        public void messageinit()
        {
            jdif = (int)(nrad - 1);
            for (int j = NWK - 1; j >= 0; j--)
            {
                iupb[j] = nrad - 1;
                ilob[j] = 0;
                nc = (int)j;
                if (jdif > minint)
                {
                    return;
                }
                jdif = (int)((jdif + 1) * nrad - 1);
            }
            throw new Exception("NWK too small in arcode.");
        }

        public void codeone(int ich, byte[] code, ref int lcd)
        {
            if (ich > nch)
            {
                throw new Exception("bad ich in Arithcode");
            }
            advance(ich, code, ref lcd, 1);
        }

        public int decodeone(byte[] code, ref int lcd)
        {
            int ja = (byte)code[lcd] - ilob[nc];
            for (int j = nc + 1; j < NWK; j++)
            {
                ja *= (int)nrad;
                ja += (byte)code[lcd + j - nc] - ilob[j];
            }
            int ihi = (int)(nch + 1);
            int ich = 0;
            while (ihi - ich > 1)
            {
                int m = (int)((ich + ihi) >> 1);
                if (ja >= multdiv(jdif, ncumfq[m], (int)ncum))
                {
                    ich = (int)m;
                }
                else
                {
                    ihi = m;
                }
            }
            if (ich != nch)
            {
                advance(ich, code, ref lcd, -1);
            }
            return ich;
        }

        public void advance(int ich, byte[] code, ref int lcd, int isign)
        {
            int jh = multdiv(jdif, ncumfq[ich + 1], (int)ncum);
            int jl = multdiv(jdif, ncumfq[ich], (int)ncum);
            jdif = jh - jl;
            arrsum(ilob, iupb, jh, NWK, (int)nrad, nc);
            arrsum(ilob, ilob, jl, NWK, (int)nrad, nc);
            int j = nc;
            for (; j < NWK; j++)
            {
                if (ich != nch && iupb[j] != ilob[j])
                {
                    break;
                }
                if (isign > 0)
                {
                    code[lcd] = (byte)ilob[j];
                }
                lcd++;
            }
            if (j + 1 > NWK)
            {
                return;
            }
            nc = j;
            for (j = 0; jdif < minint; j++)
            {
                jdif *= (int)nrad;
            }
            if (j > nc)
            {
                throw new Exception("NWK too small in arcode.");
            }
            if (j != 0)
            {
                for (int k = nc; k < NWK; k++)
                {
                    iupb[k - j] = iupb[k];
                    ilob[k - j] = ilob[k];
                }
            }
            nc -= j;
            for (int k = (int)(NWK - j); k < NWK; k++)
            {
                iupb[k] = ilob[k] = 0;
            }
            return;
        }

        public int multdiv(int j, int k, int m)
        {
            return (int)((ulong)j * (ulong)k / (ulong)m);
        }

        public void arrsum(int[] iin, int[] iout, int ja, int nwk, int nrad, int nc)
        {
            int karry = 0;
            for (int j = (int)(nwk - 1); j > nc; j--)
            {
                int jtmp = ja;
                ja /= nrad;
                iout[j] = iin[j] + (jtmp - ja * nrad) + karry;
                if (iout[j] >= nrad)
                {
                    iout[j] -= nrad;
                    karry = 1;
                }
                else
                {
                    karry = 0;
                }
            }
            iout[nc] = iin[nc] + ja + karry;
        }
    }
}
 

猜你喜欢

转载自blog.csdn.net/beijinghorn/article/details/131445440
今日推荐