上個月排查一台發出告警的工控機。日誌裡那行寫的是 alarm=0x0E12,我掃了一眼,把 E12 當十進位讀成了「三千六百一十二」,又拿文件裡的位定義表從 bit0 數到 bit12,對上了一個根本不存在的項目,圍著錯誤的方向查了大半天。後來把 0x0E12 展開成 0000 1110 0001 0010 才看清:被設成 1 的是 bit1、bit4、bit9、bit10、bit11,加起來是 2+16+512+1024+2048 = 3602,跟「3612」差的就是我把十六進位的 1 當成十位那一格。值從來沒變,變的只是寫法。
一、進位只是同一個數換一種寫法
一個數值確定之後,用幾個數碼去書寫它是可以選的。基數決定了每一位能放幾個符號、以及進一位乘幾。
| 進位 | 基數 | 數碼 | 程式碼裡的前綴 | 你會在哪見到 |
|---|---|---|---|---|
| 二進位 | 2 | 0 1 | 0b | 位元運算、暫存器、遮罩 |
| 八進位 | 8 | 0-7 | 0o(舊寫法是前導 0) | Linux 權限 chmod 755 |
| 十進位 | 10 | 0-9 | 無 | 日常、日誌裡的計數值 |
| 十六進位 | 16 | 0-9 A-F | 0x | 顏色、記憶體位址、協定位元組流 |
十六進位之所以在工程裡到處都是,是因為它和二進位是 4 位一組的整倍數關係:1 個十六進位位正好壓住 4 個二進位位,人看一眼 0xFF 就知道那是 8 位全 1。
二、手算只需要三招
- 十進位轉別的:反覆除以基數取餘數,餘數倒著排。以 3756 轉十六進位為例:3756 ÷ 16 = 234 餘 12(C),234 ÷ 16 = 14 餘 10(A),14 ÷ 16 = 0 餘 14(E),倒排得
0xEAC。 - 別的轉十進位:按位權展開。EAC = 14×256 + 10×16 + 12 = 3756,能回到原值說明上一步沒寫錯。
- 二進位 ↔ 十六進位:從右往左每 4 位切一刀,逐組查表。3756 的二進位是
1110 1010 1100,三組分別是 E、A、C。
順便驗證一個不常見的:12345678 用 36 進位(0-9 加 A-Z)寫出來是 7CLZI,按權展開 7×36⁴ + 12×36³ + 21×36² + 35×36 + 18 = 11757312 + 559872 + 27216 + 1260 + 18 = 12345678,正好還原。短網址、訂單後綴那類「字母數字混合碼」基本上都是 36 進位。
三、常碰到的那幾個數,記住比現場換算快
| 十進位 | 二進位 | 十六進位 | 八進位 | 典型出處 |
|---|---|---|---|---|
| 255 | 1111 1111 | FF | 377 | RGB 單通道上限、8 位全 1 |
| 420 | 110100100 | 1A4 | 644 | chmod 644 的真實數值 |
| 493 | 111101101 | 1ED | 755 | chmod 755 的真實數值 |
| 4096 | 1 0000 0000 0000 0000 | 1000 | 10000 | 一頁記憶體的位元組數 |
| 65535 | 16 個 1 | FFFF | 177777 | 連接埠號上限、無號 16 位 |
| 16777215 | 24 個 1 | FFFFFF | 77777777 | 24 位色的白 #FFFFFF |
| 2147483647 | 31 個 1 | 7FFFFFFF | 17777777777 | 32 位有號整數上限,介面溢出的報錯值 |
| 4294967295 | 32 個 1 | FFFFFFFF | 37777777777 | 無號 32 位、IPv4 廣播位址 |
八進位那一欄有個實用結論:755 和 493 是同一個權限的兩種寫法。設定檔裡存哪個都行,怕的是混著用——我看過同一段腳本一處寫 755、一處寫 493,diff 裡像改了兩遍。
四、我現在實際怎麼處理
- 先把日誌裡的原始寫法照抄下來,保留
0x、0b前綴,不要憑記憶重敲,E12 那種事故一半來自手抄。 - 數值本身我會用進位轉換器算:選好來源進位,二、八、十、十六的結果同螢幕給出,省掉我一邊按計算機一邊對齊位數。位元遮罩要逐位勾的時候,我盯的是那個二進位輸出,不是十六進位。
- 位元組流要按字元看的時候用它另一個方向:文字轉成每個字元的進位編碼,
A1會得到41 31,分隔符可選,擷取封包時對照協定表逐位元組核。 - 顏色那種六位十六進位我不再手拆。以前把
#FF8A00拆成三段再除 255,經常漏掉進位,直接用顏色轉換把 HEX 和 RGB、HSL 對齊更省事。
如果只是想統一文字裡的全形半形或看 ASCII 值,文字編碼轉換更對口,它管的是字元層面,不是數值進位。
五、容易踩的三處
前綴和預設基數不一致。JavaScript 裡 parseInt('0755') 得到 755,parseInt('0755', 8) 才是 493;而字面量 0755 在嚴格模式和 ES Module 下直接拋 SyntaxError,八進位必須寫 0o755。同一份權限值,三種寫法三種結果,跨語言傳設定時務必寫全基數。
大數會先丟精度再談進位。雙精度浮點只能精確表示到 2⁵³−1 = 9007199254740991,再往上 9007199254740993 會被存成 ...992。這類數換算前得確認工具按 BigInt 走,否則十六進位看著正常、尾數已經錯了。
負數沒有「標準十六進位」。-1 寫成 FFFFFFFF 只在 32 位補數這個前提下成立,換 16 位就是 FFFF。進位轉換處理的是數值本身,位寬要自己帶上。
六、常見疑問
Q:為什麼電腦非要用二進位?
物理上兩個狀態最穩(高電位低電位),抗噪和實作成本都最低。八進位、十六進位不是給機器用的,是把幾十個 01 壓縮成人眼能掃的寫法。
Q:十六進位小數怎麼轉?
整數除基取餘,小數是乘基取整:0.5 × 16 = 8.0,所以 0.5 寫成 0.8。但 0.1 在任何以 2 為基的進位下都是無限循環的,這正是浮點誤差的來源之一,別指望換一種進位能消掉它。
Q:0xFF 和字串「FF」有區別嗎?
有。前者是數值字面量,後者是文字,要明確按 16 進位解析才能得到 255。日誌、JSON、設定檔裡的「十六進位」幾乎都是字串形態,這就是很多換算錯位的起點。
Q:為什麼最多到 36 進位?
36 剛好是 10 個數字加 26 個字母。再往上要引入大小寫區分(62 進位)或其他符號,可讀性和不區分大小寫的情境都會出問題。
Q:位元遮罩怎麼核對有沒有多勾一位?
把合計值轉回二進位,逐位和文件表對齊;兩邊位序(從 0 還是從 1 起數)確認一次。我做的是把 3602 展開成 0000 1110 0001 0010 對著表打勾,比反向把位值加起來更容易發現多出的那一位。
那台回報 0x0E12 的機器最後確認是編碼器異常加馬達過溫同時觸發,跟進位沒關係。但它讓我改掉一個習慣:凡是帶 0x、0o 的值,一律展開成二進位或十進位寫在註解裡,不靠腦內換算。人的十進位直覺是唯一會出錯的那一環。