Base91 編碼/解碼
工作原理:
輸入先變成位元組,再打包成 13 位元一組、每組變成兩個字元;在短形式會有歧義之處則改用一個 14 位元組。
輸入是:
工作原理:
空白字元會被忽略,每個字元都在本頁下方印出的字母表中查詢。未知字元會連同其位置一併報告。
輸出為:
在輸入框中按 Ctrl+Enter 即執行當前模式。
請選擇編碼或解碼並點選其操作按鈕。自測會執行 130 次編碼/解碼往返並在此報告數量。
字母表,全文印出: Base91 背後沒有標準化組織,字母表在不同實作之間不一定相同。本頁使用參考實作的字母表,下列所有值都是用表中列出的恰好 91 個字元產生的。
綠色格是可以安全出現在 HTML 文字或 URL 查詢中的字元;橘色格必須跳脫或做百分號編碼(見表下方的說明)。
綠色單元格是可以安全放在 HTML 文本或 URL 查詢串中的字元;橙色單元格必須轉義或百分號編碼(見表下方的說明)。
什麼是 Base91?
Base91 是一種只用可列印 ASCII 字元書寫任意位元組的方式,乾的活和 Base64 一樣,但每個字元塞進了更多位元。它不把輸入切成 6 位元組、每組印一個字元,而是不斷收集位元,直到湊夠至少 13 個,然後印出兩個字元,共同表示這 13(或 14)位元的值。
91 個字元的字母表中取兩個字元能命名 91 × 91 = 8281 個不同的值,而 213 = 8192,所以兩個字元總是裝得下 13 位元 — 還剩 89 個值富餘。這就是全部訣竅:每 2 字元 13 位元即每字元 6.5 位元,對比 Base64 的 6,換來約 23% 而非 33% 的開銷。
30 秒版本:test 變成 fPNKd,Hello, World! 變成 >OwJh>}AQ;r@@Y?F。解碼是精確的逆過程,且需要同一張 91 字元字母表 — 換一張字母表,同樣的位元組會得到不同的輸出。
13 和 14 位元從哪裡來
91 × 91 = 8281 個值可由一對字元命名,多於 213 = 8192 而少於 214 = 16384。所以一對字元總能承載 13 位元,有時能承載 14 位元。編碼器先試 13 位元:如果 13 位元的值大於 88,就直接印出該值;否則再取第 14 個位元,改印得到的 14 位元值。
88 這個邊界並非任意選取。兩個字元能命名的最大值是 90 + 91 × 90 = 8280,而 8192 + 89 = 8281 已經超過它。所以任何 14 位元組的低 13 位元視窗都不可能達到 89 或更高,於是解碼器僅憑值本身就能把兩種情形區分開:
| 解碼器看到的 | 消耗的位元 | 原因 |
| value & 8191 在 89 到 8191 | 13 位元 | 89 及以上的值只能來自 13 位元分支,因為低 13 位元視窗那麼高的 14 位元組至少是 8281 — 沒有一對字元能命名它。 |
| value & 8191 在 0 到 88 | 14 位元 | 編碼器只會在 13 位元讀數不超過 88 時才退回 14 位元,所以這個分支就是那條退路,別無其他。 |
結尾剩下的東西 — 1 到 13 位元不等 — 印成一個字元,剩餘值確實超過 7 位元時再加印第二個字元。這就是編碼長度按兩個字元步進、有時停在奇數的原因。
字母表,以及它捨棄的三個字元
可列印 ASCII 共 94 個字元,從 ! (0x21) 到 ~ (0x7E)。Base91 用了其中 91 個,捨棄的是連字元 -、撇號 ' 和反斜槓 \。字母、數字和幾乎所有標點都在字母表中恰好出現一次,順序如上表所示;第 92 到 94 個可列印字元只是無法被定址。
因為字母表是固定順序的,值對應的字元靠查表而非公式。兩個按不同順序排列字母表的實現都自稱“Base91”,卻讀不了彼此的輸出 — 這是最常見的困惑來源。本頁印出自己的字母表,任何輸出永遠都可以據此追溯。
Base91 與其他可列印編碼的對比
| 編碼 | 每字元位元數 | 相對輸入的大小 | 備註 |
| Base16(hex) | 4 | 200% | 每位元組兩個字元,直讀無礙,無需查表。 |
| Base32 | 5 | 160% | 大小寫不敏感、適合人口述,但它是這組裡最浪費的。 |
| Base58 | ~5.86 | ~137% | 為地址而生、不為密度:它避開了易混淆的字元。 |
| Base64 | 6 | 133% | 無處不在的預設選擇:每 3 位元組 4 個字元,最後一個字元浪費掉 6 個未用位元。 |
| Ascii85 / Base85 | 6.4 | 125% | 每 4 位元組 5 個字元,無需查表,但它要除以 85 — 見 Ascii85 轉換器。 |
| Base91 | ~6.5 | ~123% | 其中最密的:每 2 字元 13 位元,代價是時不時來一個 14 位元組。 |
百分比為各編碼的漸近代價(四捨五入);一個真實 92 位元組輸入的實測字元數在本頁上方的表格裡。Base91 已接近可列印 ASCII 所能達到的極限:每字元 7 位元意味著要用 128 個不同的字元,而可列印區段提供不了這麼多。
Base91 不是什麼
- 不是標準。沒有 RFC。參考實現是 Joachim Henke 以 BSD 3 條款許可證釋出的 basE91 0.6.0;“Base91”一詞也被用來指並非這張字母表的其他字母表。
- 不是加密。每個字元都是對位元的直接重排,任何拿到字母表的人都能一遍逆向。
- 不是 URL 安全。字母表裡有
#、%、&、/、? 和 ",它們在 URL 中都會改變含義,所以 Base91 輸出放進查詢串之前必須做百分號編碼。
- 不是 HTML 安全。它還包含
<、>、& 和 ",放進標記之前必須轉義 — 可用 HTML 實體轉換器。
- 不是定長。輸出長度取決於位元模式,而不只是位元組數,所以同樣大小的兩個輸入可能相差一個字元。
真會咬人的陷阱
- 本頁的字母表就是參考字母表。如果另一個工具對同樣的輸入給出不同輸出,先比較字母表再斷定哪邊壞了。這 91 個字元必須以相同的順序出現。
- 一個錯字能改變整個尾部。組邊界由之前的值決定,串前部的一處改動會使其後全部偏移,而尾部只用最後一組的剩餘位元解碼。
- 奇數輸出長度是正常的。它不代表字串被截斷;最後一組的剩餘值不超過 7 位元時就只印一個字元。
- 公開的示例可能帶一個你沒輸入的換行。附帶了行尾的 shell 命令,是著名 Base91 值比同樣文本長一個字元的常見原因:
echo "Hello, World!" 交給編碼器 14 位元組、印出 >OwJh>}AQ;r@@Y?FF,而純文本的 13 位元組印出 >OwJh>}AQ;r@@Y?F。兩者都對,只是位元組數不同 — 輸出框下方的計數會告訴你手裡是哪一個。
- 把二進位制資料當文本解碼行不通。解碼出的位元組不是有效 UTF-8 時,就沒有合理的文本可顯示 — 這正是十六進位制輸出模式的用途。
快問快答
「為什麼本頁顯示的值和另一個 Base91 工具相差一個字元?」
幾乎總是字母表不同。第二常見的原因是某個工具只用 13 位元分支、從不用 14 位元退路;那個變體程式碼更短,但與這裡使用的參考演算法輸出不同。
還有一個只靠數字節數就能了斷的精確情形,它解釋了流傳最廣的那個例子:末尾的換行符。大多數公開向量都是 shell 命令產出的,而 shell 會補上收尾的行結束符。Hello, World! 的 13 位元組在這裡印 16 個字元,而 Hello, World! 後跟換行符的 14 位元組印出 17 字元的 >OwJh>}AQ;r@@Y?FF。把兩個值分別粘進解碼框:本頁會報告位元組數,兩種讀數可以分得清清楚楚,不用爭論。示例按鈕會根據你正在使用的讀取方式載入兩者中對應的一個,並跟隨編碼/解碼開關和輸入是/輸出為選擇器:文本讀取方式得到 16 字元的值,十六進位制讀取方式得到帶末尾 0A 位元組一目瞭然的 17 字元值,編碼器則得到要編碼的文本而不是 Base91 字串。
「Base91 比 Base64 小嗎?」
是的,按編碼後的大小約小 8%:每 2 字元 13 位元對比每字元 6 位元。在 92 位元組文本上,Base64 印 124 個字元,Base91 印 114 個。
「我能用 Base64 解碼器解這個嗎,或者反過來?」
不能。兩種字母表在字母和數字上重疊,所以 Base64 解碼器會把 Base91 字串當“有效”字元接受,然後默默產出亂碼。兩者除了方案的外形以外毫無一致之處。
「輸出像 Base64 一樣以填充結尾嗎?」
不會。Base91 根本沒有填充字元:結尾剩下的位元就作為最後一組印出,不需要新增或去除任何東西。
「我怎麼知道本頁的結果是對的?」
按自測 0 到 64 位元組。它對 130 種位元組模式做編碼再解碼,覆蓋 13/14 位元分界發生變化的每個長度,並把回來的位元組與送進去的位元組逐一比對。示例按鈕的向量還額外與本站測試工具中第二個獨立實現交叉校驗過。
標準與致謝
本頁實現了 Joachim Henke 的 basE91 參考實現(0.6.0 版)的演算法,該實現以 BSD 3 條款許可證釋出,字母表印於上方。解碼接受並忽略空白字元,與參考工具一致。
本頁文字為本站撰寫;演算法並非從任何地方抄來。示例按鈕的值是參考包自帶的那一個示例(標註“參考示例”),加上本站測試工具中儲存的第二個獨立實現產出的值(標註“交叉校驗”)。