原創反轉精度算法:小數的終極編碼

上期帶大家嚐鮮了Zipack格式的“多快好省”:“多”指功能多;“快”指解析快;“省”指體積小。不過用戶最好奇的一定是Zipack的底層原理,畢竟它“囂張”地宣稱擁有比UTF8和IEEE浮點數還棒的編碼。這期詳細介紹Zipack底層是如何通過原創的小數編碼“反轉精度算法”來取代經典的IEEE浮點數的。

目前主流的小數編碼自然是IEEE浮點數,早在之前的一期《IEEE浮點數的設計缺陷》中就已經談過IEEE浮點數的優缺點了,這裏總結一下那期的結論:

IEEE浮點是經典的定長浮點數編碼,兼容整數,也有許多優秀的思想,比如通過“隱式補一”的方法消除有效位的冗餘,又讓小數點從左端浮動,降低了指數位的絕對值。如果你聽不懂我在說什麼,參考下之前文章吧。

但是IEEE浮點數被淘汰的原因在於,違反了信息論“一一映射”的原則。比如它區分+0和-0,還有好多編碼段都代表NaN。這些瑕疵在Zipack中是無法容忍的:Zipack的每種類型都是一一映射的,換句話講,隨機寫一段二進制比特流都可以解析出合法的Zipack對象。

“精反算法”。。有內味了

咦?怎麼有股“精神分裂,反社會人格”的味道在裏面,算了就這樣吧。

那反轉精度算法(簡稱精反算法或精反編碼)到底是怎麼玩的呢?這裏又要引出一個背景知識:VLQ偏移自然數【怎麼樣,Zipack複雜吧】。原理就不復述了,只要知道它是一個“一一映射”的自然數編碼,而且是變長且無上限的。用VLQ偏移自然數可以表示任意一個自然數(0,1,2....)的二進制形式。“精反算法”的思路就是通過2個自然數表示一個小數:一個表示整數部分,一個表示小數部分。

在Zipack的“Number Family”中有5種實數類型,分別是小自然數、正整數、負整數、正小數、負小數。5種類型互補,意味着它們之間沒有重合部分,理論上能表示實數軸上所有的數,只要允許無限長的字節。其中和精反算法相關的是正小數和負小數,由於正負小數完全對稱,我們只要考慮無符號的正小數的情況就行了。

發揮想象,將每個無符號小數用字符串的形式表示,這樣它就可以被小數點分爲左右兩部分:整數部分和小數部分。左部正好是一個自然數,用一個VLQ就能表示;右部而言,也可以用一個自然數唯一表示,但需要一些技巧:首先根據小學數學的知識點【小數點後末位的0無意義】確定最後一位一定是個“1”,再根據幼兒園數學的知識點【正整數的最高位一定是1】,我們成功將小數部分的倒序和正整數一一映射起來。(以上都是二進制的情況)

至於VLQ偏移自然數是從0開始的,我們只要給它平移1個單位就相當於從1開始了。下面是一個實例描述如何利用精反算法編碼二進制小數110.0101

  1. trim:去除兩端無意義的“0”

  2. split:將110.0101分割成110和0101兩部分

  3. encode:將左部的110編碼成VLQ自然數,記作A

  4. reverse:將右部的0101反轉成1010

  5. offset:1010 - 1 = 1001

  6. encode:將1001編碼成VLQ自然數,記作B

  7. concat:將A與B無縫拼接,輸出AB

這就是精妙絕倫的精反算法,七個步驟,簡單易懂。之所以比IEEE浮點數更棒,因爲精反算法做到一一映射,既沒有歧義也沒有冗餘,更沒有上限(VLQ的性質決定)。

在上面的例子中,我們得到AB以後還需要加上一個前綴才能合成一個Zipack對象:正小數的前綴是0xF2,負小數的前綴是0xF3。這些前綴通常是一個字節,用來表示接下來的對象的類型。我們可以去Zipack官網上體驗一把精反算法的壓縮效率:

在如圖的這個例子中,十進制的“-0.125”首先被轉換成二進制的“-0.001”,然後序列化成Zipack的負小數類型:[F3 00 03]。其中F3表示負小數,00表示整數部分,03表示小數部分。

精反算法只是Zipack的核心之一,Zipack的規範文檔裏記錄着所有的核心思想和設計理念,讓Zipack的性能甩JSON一大截。目前Zipack仍處於推廣階段,急需你這樣的優秀人才。Gitee倉庫:https://gitee.com/zipack/spec

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章