JavaScript: 圧縮とかPAQ8もどき

かつて圧縮業界で最強を誇った**PAQ8**を軽量化した`fp8`の紹介。例によって文脈混合法により圧縮しまくっていきます。

## 実装
本家よりとんでもなく手抜きしまくっています。jpegとかexeとか色々対応が欠落。全部実装すると莫大な量になります。ちなみに**超低速**です。
```js
function hash(a,b,c){
	c=((a>>>16)*52643+(c=a&65535)*3051<<16)+c*52643;
	c+=((b>>>16)*55539+(c=b&65535)*457<<16)+c*55539+4064955751;
	return c^c>>>9^a>>>2^b>>>3^201326591
}
function hash3(a,b,c,d){
	d=((a>>>16)*52643+(d=a&65535)*3051<<16)+d*52643;
	d+=((b>>>16)*55539+(d=b&65535)*457<<16)+d*55539;
	d+=((c>>>16)*271+(d=c&65535)*763<<16)+d*271+4114959990;
	return d^d>>>9^a>>>2^b>>>3^c>>>4^67108864
}
function squash(d,w){
	if(d>2047)return 4095;
	if(d<-2047)return 0;
	w=d&127;
	return SQ[d=(d>>7)+16]*(128-w)+SQ[d+1]*w+64>>7
}
const MAXLEN=0xfffe,
SQ=new Int16Array([1,2,3,6,10,16,27,45,73,120,194,310,488,747,1101,1546,2047,2549,2994,3348,3607,3785,3901,3975,4022,4050,4068,4079,4085,4089,4092,4093,4094]),
ST=new Uint8Array([1,2,0,0,3,5,1,0,4,6,0,1,7,10,2,0,8,12,1,1,9,13,1,1,11,14,0,2,15,19,3,0,16,23,2,1,17,24,2,1,18,25,2,1,20,27,1,2,21,28,1,2,22,29,1,2,26,30,0,3,31,33,4,0,32,35,3,1,32,35,3,1,32,35,3,1,32,35,3,1,34,37,2,2,34,37,2,2,34,37,2,2,34,37,2,2,34,37,2,2,34,37,2,2,36,39,1,3,36,39,1,3,36,39,1,3,36,39,1,3,38,40,0,4,41,43,5,0,42,45,4,1,42,45,4,1,44,47,3,2,44,47,3,2,46,49,2,3,46,49,2,3,48,51,1,4,48,51,1,4,50,52,0,5,53,43,6,0,54,57,5,1,54,57,5,1,56,59,4,2,56,59,4,2,58,61,3,3,58,61,3,3,60,63,2,4,60,63,2,4,62,65,1,5,62,65,1,5,50,66,0,6,67,55,7,0,68,57,6,1,68,57,6,1,70,73,5,2,70,73,5,2,72,75,4,3,72,75,4,3,74,77,3,4,74,77,3,4,76,79,2,5,76,79,2,5,62,81,1,6,62,81,1,6,64,82,0,7,83,69,8,0,84,71,7,1,84,71,7,1,86,73,6,2,86,73,6,2,44,59,5,3,44,59,5,3,58,61,4,4,58,61,4,4,60,49,3,5,60,49,3,5,76,89,2,6,76,89,2,6,78,91,1,7,78,91,1,7,80,92,0,8,93,69,9,0,94,87,8,1,94,87,8,1,96,45,7,2,96,45,7,2,48,99,2,7,48,99,2,7,88,101,1,8,88,101,1,8,80,102,0,9,103,69,10,0,104,87,9,1,104,87,9,1,106,57,8,2,106,57,8,2,62,109,2,8,62,109,2,8,88,111,1,9,88,111,1,9,80,112,0,10,113,85,11,0,114,87,10,1,114,87,10,1,116,57,9,2,116,57,9,2,62,119,2,9,62,119,2,9,88,121,1,10,88,121,1,10,90,122,0,11,123,85,12,0,124,97,11,1,124,97,11,1,126,57,10,2,126,57,10,2,62,129,2,10,62,129,2,10,98,131,1,11,98,131,1,11,90,132,0,12,133,85,13,0,134,97,12,1,134,97,12,1,136,57,11,2,136,57,11,2,62,139,2,11,62,139,2,11,98,141,1,12,98,141,1,12,90,142,0,13,143,95,14,0,144,97,13,1,144,97,13,1,68,57,12,2,68,57,12,2,62,81,2,12,62,81,2,12,98,147,1,13,98,147,1,13,100,148,0,14,149,95,15,0,150,107,14,1,150,107,14,1,108,151,1,14,108,151,1,14,100,152,0,15,153,95,16,0,154,107,15,1,108,155,1,15,100,156,0,16,157,95,17,0,158,107,16,1,108,159,1,16,100,160,0,17,161,105,18,0,162,107,17,1,108,163,1,17,110,164,0,18,165,105,19,0,166,117,18,1,118,167,1,18,110,168,0,19,169,105,20,0,170,117,19,1,118,171,1,19,110,172,0,20,173,105,21,0,174,117,20,1,118,175,1,20,110,176,0,21,177,105,22,0,178,117,21,1,118,179,1,21,110,180,0,22,181,115,23,0,182,117,22,1,118,183,1,22,120,184,0,23,185,115,24,0,186,127,23,1,128,187,1,23,120,188,0,24,189,115,25,0,190,127,24,1,128,191,1,24,120,192,0,25,193,115,26,0,194,127,25,1,128,195,1,25,120,196,0,26,197,115,27,0,198,127,26,1,128,199,1,26,120,200,0,27,201,115,28,0,202,127,27,1,128,203,1,27,120,204,0,28,205,115,29,0,206,127,28,1,128,207,1,28,120,208,0,29,209,125,30,0,210,127,29,1,128,211,1,29,130,212,0,30,213,125,31,0,214,137,30,1,138,215,1,30,130,216,0,31,217,125,32,0,218,137,31,1,138,219,1,31,130,220,0,32,221,125,33,0,222,137,32,1,138,223,1,32,130,224,0,33,225,125,34,0,226,137,33,1,138,227,1,33,130,228,0,34,229,125,35,0,230,137,34,1,138,231,1,34,130,232,0,35,233,125,36,0,234,137,35,1,138,235,1,35,130,236,0,36,237,125,37,0,238,137,36,1,138,239,1,36,130,240,0,37,241,125,38,0,242,137,37,1,138,243,1,37,130,244,0,38,245,135,39,0,246,137,38,1,138,247,1,38,140,248,0,39,249,135,40,0,250,69,39,1,80,251,1,39,140,252,0,40,249,135,41,0,250,69,40,1,80,251,1,40,140,252,0,41]),
DT=function(a,b){for(;b;)a[--b]=16384/(b+b+3)|0;return a}(new Int16Array(1024),1024),
ctype=new Uint8Array([0,5,5,5,5,5,5,5,5,3,3,3,3,3,5,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,3,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,6,6,6,6,6,6,6,6,6,6,2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,4]),
//Logarithm
ilog=function(a,b,c){for(;b<65536;a[b++]=c>>>24)c+=774541002/(b*2-1)|0;return a}(new Uint8Array(65536),2,14155776),
stretch=function(a,b,c,d){for(;++c<2048;)for(d=squash(c);b<d;)a[++b]=c;a[++b]=c-1;return a}(new Int16Array(4096),-1,-2048);

function rnd(){
	var A=new Uint32Array(65),a=0;
	for(A[0]=123456789,A[1]=987654321;a<62;)A[a+2]=(A[a++]*23>>>4)+A[a]*11;return A
}
function dotProduct(t,w,n,p){
	for(var s=0;n>1;)s+=t[--n]*w[n+p]+t[--n]*w[n+p]>>8;
	return s
}
//Mixer-combines models using neural networks
function Mixer(n,m,s,w){
	this.tx=new Int16Array(this.N=n+7&-8),this.wx=new Int16Array(m*=this.N);
	this.cxt=new Int32Array(s),this.pr=new Int32Array(s);
	if(s>1)this.mp=new Mixer(s,1,1);
	for(;s;)this.pr[--s]=2048;
	if(w)for(;m;)this.wx[--m]=w
}
Mixer.prototype={
base:0,ncxt:0,nx:0,
update:function(y,i,e,n,p,w){
	for(i=this.ncxt,y<<=12;i;)
		if(e=(y-this.pr[--i])*14)
			for(n=this.nx,p=this.cxt[i]*this.N+n;n;this.wx[p]=w<-32768?-32768:32767<w?32767:w)
				w=this.wx[--p]+(1+(this.tx[--n]*e>>16)>>1);
	this.nx=this.base=this.ncxt=0
},
add:function(x){this.tx[this.nx++]=x},
set:function(cx,range){
	this.cxt[this.ncxt++]=this.base+cx;this.base+=range
},
p:function(y){
	for(;this.nx&7;)this.tx[this.nx++]=0;
	if(this.mp){
		this.mp.update(y);
		for(var i=this.ncxt;i;)
			this.mp.add(stretch[this.pr[--i]=squash(dotProduct(this.tx,this.wx,this.nx,this.cxt[i]*this.N)>>5)]);
		this.mp.set(0,1);
		return this.mp.p()
	}return this.pr[0]=squash(dotProduct(this.tx,this.wx,this.nx,0)>>8)
}};
//////////////////////////// StateMap, APM //////////////////////////
function StateMap(i){
	var A=new Uint32Array(i);
	if(A.fill)A.fill(1<<31);
	else for(;i;)A[--i]=1<<31;A.cxt=0;return A}
function APM1(n){
	for(var i=33,p=i,A=new Uint32Array(n*=i);i;)A[--i]=squash(i-16<<7)*16;
	for(A.index=0;p<n;)A[p++]=A[i++];
	return A
}
function APM1p(t,y,pr,cxt){
	pr=stretch[pr];
	var g=(y<<16|y<<7)-y-y,i=t.index;
	t[i]+=g-t[i++]>>7;t[i]+=g-t[i]>>7;g=pr&127;
	return t[i=t.index=(pr+2048>>7)+cxt*33]*(128-g)+t[i+1]*g>>11
}
/*function APM(n){
	for(var i=24,p=i,A=new Uint32Array(n*=i);i;)A[--i]=squash(((i*2+1<<12)/48|0)-2048)<<20|6;
	for(A.cxt=0;p<n;)A[p++]=A[i++];
	return A
}
function APMp(A,y,pr,cx){
	SMup(A,y,255),
	pr=(stretch[pr]+2048)*23,
	cx=cx*24+(pr>>12),
	A.cxt=cx+((pr&=4095)>>11);
	return(A[cx]>>>13)*(4096-pr)+(A[cx+1]>>>13)*pr>>19
}*/
function SMup(A,y,limit){
	var c=A.cxt,i=A[c],n=i&1023,p=i>>>10;
	A[c]+=((y<<22)-p>>3)*DT[n]&-1024|n<limit
}
function SMp(A,y,cx){
	SMup(A,y,1023);
	return A[A.cxt=cx]>>>20
}
/////////////////////////// ContextMap /////////////////////////
function SmallStatesMap(m){
	var t=new Uint16Array(m>>=1);t.m=m-256;
	if(t.fill)t.fill(32768);
	else for(;m;)t[--m]=32768;
	t.cp=t.cxt=0;return t
}
function scmset(t,cx){t.cxt=cx*256&t.m}

function scmix(t,m,y,c0,rate){
	t[t.cp]+=(y<<16)-t[t.cp]+(1<<rate-1)>>rate;
	m.add(stretch[t[t.cp=t.cxt+c0]>>4])
}
function ContextMap(m,c,f){
	this.cxt=new Uint32Array(c);
	this.runp=new Uint32Array(c);
	this.cp=new Uint32Array(c);
	this.cp0=new Uint32Array(c);
	for(this.sm=[],this.fast=f;c;this.sm[c]=StateMap(256))this.runp[--c]=3;
	this.last=new Uint8Array(m>>=6);
	this.chk=new Uint16Array(m*7);
	this.bh=new Uint8Array(m*49);
	this.Rnd=rnd(this.m=m-1)
}
ContextMap.prototype={cn:0,
set:function(c,n){
	var i=this.cn++;i&=n||-1;
	c=((c>>>16)*26803+(c&=65535)*15070<<16)+c*26803+i;c=c<<16|c>>>16;
	this.cxt[i]=((c>>>16)*52503+(c&=65535)*1883<<16)+c*52503+i>>>0},
get:function(bh,chk,ch,o){
	var last=this.last,l=last[o],m=o*7,n=o*49;
	if(chk[m+(l&15)]===ch)return(l&15)*7+n;
	for(var b=65535,bi=0,i=7,p;i;){
		if(chk[m+--i]===ch)return last[o]=l<<4|i,i*7+n;
		p=bh[i*7+n];
		if(p<b&&15&l^i&&l>>4^i)b=p,bi=i
	}last[o]=240|bi,chk[m+bi]=ch;
	for(p=7,n+=bi*7;p;)bh[n+--p]=0;
	return n},
mix:function(m,cc,bp,c1,y){
	for(var i=this.cn,c,h,p,x,bh=this.bh,cp=this.cp,cp0=this.cp0,chk=this.chk,runp=this.runp,cxt=this.cxt,Rnd=this.Rnd,result=0;i;){
		if(h=cp[--i]){
			c=ST[bh[h]<<2|y];
			if(c>203&&(p=++Rnd[64],Rnd[p&63]=Rnd[p-24&63]^Rnd[p-55&63])<<(452-c>>3))c-=4;
			bh[h]=c
		}
		//Update context pointers
		if(bp>1&&!bh[runp[i]])cp[i]=0;
		else{x=cxt[i]>>>16;
			switch(bp){
			case 1:case 3:case 6:cp[i]=cp0[i]+1+(cc&1);break;
			case 4:case 7:cp[i]=cp0[i]+3+(cc&3);break;
			case 2:case 5:cp[i]=cp0[i]=this.get(bh,chk,x,cxt[i]+cc&this.m);break;
			default:
				h=cp[i]=cp0[i]=this.get(bh,chk,x,cxt[i]+cc&this.m);
				//Update pending bit histories for bits 2-7
				if(bh[h+=3]===2)
					c=bh[h+1]+256,
					bh[p=this.get(bh,chk,x,cxt[i]+(c>>6)&this.m)]=1+(c>>5&1),
					bh[p+1+(c>>5&1)]=1+(c>>4&1),
					bh[p+3+(c>>4&3)]=1+(c>>3&1),
					bh[p=this.get(bh,chk,x,cxt[i]+(c>>3)&this.m)]=1+(c>>2&1),
					bh[p+1+(c>>2&1)]=1+(c>>1&1),
					bh[p+3+(c>>1&3)]=1+(c&1),
					bh[h+3]=0;
				//Update run count of previous context
				if(!bh[p=runp[i]])bh[p]=2,bh[p+1]=c1;//new context
				else if(bh[p+1]^c1)bh[p]=1,bh[p+1]=c1;//different byte in context
				else if(bh[p]<254)bh[p]+=2;//same byte in context
				runp[i]=h
			}
		}
		if(!this.fast)
			//predict from last byte in context
			if(bh[p=runp[i]+1]+256>>8-bp^cc)m.add(0);
			else c=bh[p-1],//count*2,+1 if 2 different bytes seen
				p=(bh[p]>>7-bp&1)*2-1,//predicted bit+for 1,-for 0
				m.add(p*ilog[c+1]<<2+(~c&1));
		//predict from bit context
		if(p=cp[i])p=bh[p],p&&result++;
		m.add(stretch[SMp(this.sm[i],y,p)])
	}
	if(bp>6)this.cn=0;
	return result}
};
/////////////////////////// dmc model /////////////////////////
function dmcModel(m){
	if(m<65536)m=65536;
	this.state=new Uint8Array(this.top=this.size=m*=2);
	this.nx=new Uint32Array(m*2);
	this.c=new Uint16Array(m*2);
	this.sm=StateMap(256)
}
dmcModel.prototype={
curr:0,th:256,
add:function(m,y){
	//clone next state
	var N=this.nx,C=this.c,S=this.state,a=this.curr,p=this.top,s=this.size,x=a+y*s;
	if(p<s){
		var n=N[x],i=C[x],j=C[n]+C[n+s];
		if(i>=this.th*2&&j-i>=this.th*3){
			i=i*4096/j|0;
			C[n]-=C[p]=C[n]*i>>12;
			C[n+s]-=C[p+s]=C[n+s]*i>>12;
			N[p]=N[n];N[p+s]=N[n+s];
			S[N[x]=p++]=S[n];
//			if(top==MEM*2)this.th=512;if(top==MEM*3)this.th=768
		}
	}
	//Initialize to a bytewise order 1 model at startup or when flushing memory
	if(p>=s)for(i=this.th=256,p=i*i,a=0,x=y*s;i--;)
		for(j=256;j;i<127?(N[n]=n+i+1,N[n+s]=n+i+2):(N[n]=i-127<<8,N[n+s]=i+1<<8))
			C[n=--j<<8|i]=C[n+s]=192;
	this.top=p;
	//update count,state
	C[x]<3800&&(C[x]+=256);
	S[a]=ST[S[a]<<2|y];
	//predict
	m.add(stretch[SMp(this.sm,y,S[x=this.curr=N[x]])]);n=C[x+s];
	m.add(stretch[(n+5)*4096/(C[x]+n+10)|0])
}};

function Predictor(m){
	if(m>9)m=9;m=1<<m+16;
	this.buf=new Uint8Array(m*8);this.bm=m*8-1;
	this.dmc=new dmcModel(m);
	this.a=APM1(256);this.a1=APM1(1<<16);this.a2=APM1(1<<16);
	// match model
	this.H=new Int32Array(m);this.hm=m-1;
	this.scm1=SmallStatesMap(1<<17);
	this.scm2=SmallStatesMap(1<<17);
	// contextModel2
	this.cm=new ContextMap(m*32,22);
	this.m=new Mixer(76,1288,5);
	this.cxt=new Uint32Array(16);//order 0-11 contexts
	// normal model
	this.t1=new Uint32Array(256);
	this.t2=new Uint16Array(1<<16)
}
Predictor.prototype={
c0:1,c4:0,bpos:0,
// match model
h:0,ptr:0,len:0,result:0,posnl:-1,pos:-1,
match:function(m,y,bp){
	var B=this.buf,l=this.len,c=B[this.pos&this.bm],p,a=0,b=0;
	if(!bp){
		this.h=this.h*7976+c+1&this.hm;//update context hash
		if(l)++l,++this.ptr;
		else{
			p=this.ptr=this.H[this.h];
			if(p&&this.pos-p<this.bm)
				for(;B[this.pos-l&this.bm]===B[p+~l&this.bm]&&l<MAXLEN;)++l;
		}
		scmset(this.scm1,this.H[this.h]=this.pos+1);
		this.result=l;
		if(c===255||c===13||c===10)this.posnl=this.pos;
		p=this.pos-this.posnl,scmset(this.scm2,p<255?p:255)
	}
	if(l)
		if(c===B[this.ptr-1&this.bm]&&this.c0===(p=B[this.ptr&this.bm])+256>>8-bp){
			if(l>MAXLEN)l=MAXLEN;
			a=ilog[l]<<2;b=l<32?l<<6:2048;
			p>>7-bp&1||(a=-a,b=-b)
		}else l=0;
	m.add(a),m.add(b);
	this.len=l;scmix(this.scm1,m,y,this.c0,7);scmix(this.scm2,m,y,this.c0,7);
	return this.result},

word0:1,word1:0,mask:0,mask2:0,
contextModel2:function(y,bp){
	this.m.update(y);
	var im=ilog[this.match(this.m,y,bp)],B=this.buf,c1=B[this.pos&this.bm],c2=B[this.pos-1&this.bm];//Length of longest matching context+4
	this.dmc.add(this.m,y);

	//Normal model
	if(!bp){
		for(var i=14,c4=this.c4,c3=c4&255,cxt=this.cxt;i;)cxt[i]=hash(cxt[--i],c3);	//update order 0-11 context hashes
		this.cm.set(0);
		this.cm.set(c3);
		this.cm.set(c4&65535);
		this.cm.set(c4&0xffffff);
		this.cm.set(c4);
		this.cm.set(cxt[5]);
		this.cm.set(cxt[6]);
		this.cm.set(cxt[14]);
		//sparse model
		this.cm.set(c4&0xf8f8c0ff);
		this.cm.set(c4&0xe0e0e0);
		this.cm.set(c4&0xffc0ff80);
		this.cm.set(im|c4&-65536);
		this.cm.set(im|c4&0xff00);
		this.cm.set(im|c4&0xff0000);
		this.cm.set(this.mask=this.mask<<3|ctype[c3]);
		this.mask2=this.mask2<<3|this.mask>>27&7;
		this.cm.set(hash(this.mask<<5,this.mask2<<2));
		//indirect model
		c3=B[this.pos-2&this.bm]<<8|c2;
		this.t1[c2]=this.t1[c2]<<8|c1;
		this.t2[c3]=this.t2[c3]<<8|c1;
		c3=c2<<8|c1|this.t2[c2<<8|c1]<<16,c1|=this.t1[c1]<<8;
		this.cm.set(c1>>8&65535),this.cm.set(c3>>16&255),
		this.cm.set(c1&65535),this.cm.set(c3&0xffffff);

		//word/exe model
//		if(filetype==EXE)this.cm.set(execxt(4)),this.cm.set(execxt(5));
//		else{
			c3=c1&=255;
			if(c3>64&&c3<91)c3+=32;
			if(c3>96&&c3<123||c3>127)this.word0=hash(this.word0,c3);
			else if(this.word0)this.word1=this.word0,this.word0=0;
			this.cm.set(this.word0);
			this.cm.set(hash(this.word0,this.word1))
//		}
	}
	i=this.cm.mix(this.m,this.c0,bp,c1,y);
	this.m.set(c1+8,264);
	this.m.set(this.c0,256);
	this.m.set(i+16*(c1>32)+32*!bp+64*(c1===c2)/*+128*(filetype==EXE)*/,256);
	this.m.set(c2,256);
	this.m.set(im,256);
	return this.m.p(y)},

update:function(y){
	if((this.c0+=this.c0+y)>255)
		this.buf[++this.pos&this.bm]=this.c0&=255,
		this.c4=this.c4<<8|this.c0,
		this.c0=1;
	var p0=this.contextModel2(y,this.bpos=this.bpos+1&7),p=APM1p(this.a,y,p0,this.c0),p2=this.buf[this.pos&this.bm],p1=APM1p(this.a1,y,p0,this.c0|p2<<8);
	return p*5+p1*15+APM1p(this.a2,y,p0,65535&hash3(this.bpos,p2,this.buf[this.pos-1&this.bm]))*12+16>>5
}};
//setImmediateもどき
(function(a){
	var F=[],hit="\0";
	a.wait0=function(f){
		F[F.length]=f;a.postMessage(hit,"*")
	};
	a.onmessage=function(e){
		if(e.source===a&&e.data===hit)
			e.stopPropagation(),F[0]&&F.shift()()
	}
})(this);
/* FP8enc(A,m,done,rate): compressor
@A: input(Array / Uint8Array)
@m: memory usage(0-9). (65536<<m)*44 bytes
@done: call back of last process.
	done(A,a,z)
	@A: compressed array of input.
	@a: last position
	@z: compressed size
@rate: call back of progress
	rate(a,z)
	@a: current position
	@z: last position
@return:
	call with await: compressed array of @In
	without await: Promise object
*/
async function FP8enc(A,m,done=a=>a,rate){
	var a=A.length,b,z=a,w=0,x=-1,o=1,p=2048,
		O=[m&=15],P=new Predictor(m),
		pass="function"!=typeof rate?-1:1023,fn=b=>wait0(c=>b(rate(a,z))),st=+new Date;
	//write size
	for(;a;a>>>=8)O[o++]=a&255,O[0]+=16;
	//main loop
	for(;a<z;a&pass||Date.now()-st<200||await new Promise(fn,st=+new Date))
		for(let i=8,s=A[a++];i;){
			p+=p<2048;m=w+(x-w>>>12)*p+((x-w&4095)*p>>12);
			p=P.update(b=s>>--i&1);
			for(b?x=m:w=m+1;!((w^x)>>24);x=x<<8|255)
				O[o++]=x>>>24,w<<=8
	}
	O[o++]=w>>>24;done(O,z,o);
	return O
}
/* FP8dec(A,done,rate): decompresser
@A: input(Array / Uint8Array)
@done: call back of last process
	done(A,a,z)
	@A: compressed array of input
	@a: input size
	@z: compressed size
@rate: call back of progress
	rate(a,z)
	@a: current position
	@z: last position
@return:
	call with await: decompressed array of @A
	without await: Promise object
*/
async function FP8dec(A,done,rate){
	var a=1,b,c,l=0,m,o=0,p=2048,v=A[0],w=0,x,y=v>>4,z=A.length,
		O=[],P=new Predictor(v&15),
		pass="function"!=typeof rate?-1:1023,fn=b=>wait0(c=>b(rate(a,z))),st=+new Date;

	for(;y--;w+=8)l+=A[a++]<<w;
	for(v=w=0;o<l;o&pass||Date.now()-st<200||await new Promise(fn,st=+new Date)){
		for(c=1;c<256;c+=c+b){
			for(p+=p<2048;!((w^x)>>24);x=x<<8|255)
				v=(v<<8|(a<z?A[a++]:255))>>>0,w=w<<8>>>0;
			m=w+(x-w>>>12)*p+((x-w&4095)*p>>12);
			p=P.update(b=v<=m|0);b?x=m:w=m+1
		}
		O[o++]=255&c
	}
	done(O,z,o);return O
}
```
```js:使用例
(async()=>{
let A=Array.from("That that is is that that is not is not is that it it is",a=>a.charCodeAt());
let rate=(a,b)=>{console.log((a/b*1e4|0)/100,"%")}, done=(a,b,c)=>{console.log(b,"->",c,a)};
let e=await FP8enc(A,5,done,rate);
let d=await FP8dec(e,done,rate);
console.log(e.length, String.fromCharCode(...d))
})()
```
file爆縮実験
<p class="codepen" data-height="300" data-default-tab="result" data-slug-hash="XWLXjVJ" data-pen-title="fp8 compressor" data-user="xezz" style="height:300px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;border:2px solid;margin:1em 0;padding:1em"> <span>See the Pen <a href="https://codepen.io/xezz/pen/XWLXjVJ">fp8 compressor</a> by xezz (<a href="https://codepen.io/xezz">@xezz</a>) on <a href="https://codepen.io">CodePen</a>.</span></p><script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

## 参考文献
[発信地](https://encode.su/threads/613-FP8-(-Fast-PAQ8))
[Large Text Compression Benchmark](https://mattmahoney.net/dc/text.html#1532)

## 補足
本記事のfp8もどきは本家と互換性は皆無です。本家に忠実な移植して下さる方は大歓迎です