雜記 › 技術筆記 › 條碼解碼只吐原文:EAN-13 檢查碼、UPC-A 的前導零與 CODE128 的模 103

條碼解碼只吐原文:EAN-13 檢查碼、UPC-A 的前導零與 CODE128 的模 103

一、解碼頁的能力面比文案窄得多

拿一張自家列印的 EAN-13 位圖去條碼解碼頁,版面上出現的不是碼文,而是「目前瀏覽器不支援條碼辨識」那一行小字。

模板一共 195 行,外部腳本 0 個,這條鏈上沒有第二套引擎兜底。第 101 行的 'BarcodeDetector' in window 是唯一判定,本機 headless Chrome 實測 false,第 102–122 行整段解碼因此不可達,只剩第 172–175 行那塊不支援的面板。

為排除圖像本身的問題,我用站內條碼產生器現畫一張合法的 EAN-13(5901234123457,實測圖 95×40 像素)餵進同一個入口,輸出 0 個字元。這張圖檢查碼合法、掃碼槍認,瀏覽器一側卻一個字都給不出。

「掃描」也不存在:模板裡 getUserMedia、video、capture 都是 0 次,第 15 行只有一個 type="file" 的靜態上傳框。

二、檢查碼是從 12 位算出來的

EAN-13 的第 13 位是模 10 加權:產生器第 188 行按索引偶數位 ×1、奇數位 ×3 求和,第 190 行取 (10 - sum % 10) % 10。實測 '590123412345' sum=83 → 7、'999999999999' sum=216 → 4。

590123412345 sum 83 → 檢查碼 7 400638133393 sum 89 → 1 690123456789 sum 128 → 2 999999999999 sum 216 → 4 加權:索引偶數位 ×1、奇數位 ×3,檢查碼 = (10 - sum % 10) % 10 給滿 13 位不重算:5901234123456 原樣成圖,正確末位應為 7

圖 1:檢查碼如何從 12 位算出,以及一個被放行的錯碼

三、給滿 13 位,一位都不重算

「明明算了檢查碼卻對不上」的困惑,根子多半在產生端。生成器第 184–194 行只在 length === 12 的分支裡算檢查碼,給滿 13 位直接落進空分支:實測 6/6 組錯檢查碼原樣成圖,'5901234123456' 應為 7、'4006381333930' 應為 1、'9999999999999' 應為 4,一個錯都不報。超過 13 位時生成器第 192–193 行直接截斷,而截斷在補位之後,超長輸入同樣不會觸發重算。圖能出來只證明數字被畫了出來,不證明它自己前後一致。

四、UPC-A 要補一個 0 才等價

兩種碼制的權重方向差一位,換算動作只有一個:11 位 UPC-A 資料前面補一個 0,再走 encodeEAN13。實測 10/10 組與 UPC-A 自身口徑同檢查碼,'04210000526'→4、'01234567890'→5 都落在這一條上。

坑是已經帶末位的 12 位 UPC-A:原樣當作 EAN-13 的 12 位資料餵進去,末位被重算成另一位,'012345678905' 變成 '0123456789050'。同一串數字在兩種口徑下合法與非法互竄,前面那種「對不上」多半就是這個。

五、CODE128 是模 103

換成模 103 就對不上前面那套:第 116–117 行取的起始碼 104 同時是檢查和初值,第 123 行按位置權重 (i+1) 累加,第 126 行做 checksum % 103。實測 'HELLO123' sum=1141 → 8、'abc' sum=502 → 90。

這張模式表本身還是壞的:patterns 陣列實測長度 184,而 CODE128 只需要 107 項;0..102 裡只有 79 種不同圖形,24 組被兩個碼值共用;終止碼 106 落在與碼值 7 完全相同的 11 模組圖形上,標準終止符應是 13 模組。後果是同圖不同文——'CS' 與 'DS' 的 bit 串逐位相同、都是 55 模組,窮舉 1296 個兩字元輸入只得到 1292 種圖形,這一族上產生到解碼的閉環並不成立。

六、CODE39 沒有檢查碼,還吞字元

生成器第 229–257 行沒有一次求和、也沒有取模,只在首尾各加一個 '*' 定界符。生成器第 230 行先 toUpperCase,'abc' 與 'ABC' 的 bit 串完全相同;表外字元更安靜,第 249 行直接丟棄,encodeCode39('A_B') 與 encodeCode39('AB') 同為 51 模組,'UU工具箱' 只剩 51 模組的 UU。

EAN-13 95 模組 · 模 10 檢查碼 CODE128 121 模組 · 模 103 加權和 CODE39 129 模組 · 沒有檢查碼 EAN-13 恆為 95 模組、與內容無關,也裝不下字母 CODE128、CODE39 兩行是 'HELLO123' 的實測模組數

圖 2:三種碼制的檢查口徑對照——模 10、模 103、無檢查

七、卡片全量渲染,複製只取第一條

第 133 行的 forEach 全量渲染,實測傳 4 個碼得到 4 張卡片、margin-top:10px 出現 3 次;但第 159 行只把 codes[0].rawValue 寫進 dataset.result,第 2、3、4 個碼看得見、複製不到。第 165 行「未辨識」與第 174 行「不支援」又都不清 dataset.result,面板換成「不支援」後舊值仍在,第 177–182 行 copyResult 照舊把它送進剪貼簿。

八、三種碼制的口徑並排

碼制檢查口徑實測與後果
EAN-13模 10 加權sum=83 → 7;給滿 13 位不重算
UPC-A補一個前導 0 後同 EAN-13'01234567890' → 5;12 位原樣餵會重算末位
CODE128模 103,權重 (i+1)'HELLO123' sum=1141 → 8;模式表只有 79 種圖形
CODE39沒有檢查碼'A_B' 與 'AB' 同為 51 模組

邊界很清楚:條碼能自證的只有「這串數字自己前後一致」。模板裡 checksum、verify、檢查、商品、真偽五個詞都是 0 次,第 134 行只把 rawValue 原樣取值,沒重算檢查碼,也就無從判定背後那件商品是不是真的。

先把碼制、位數和該不該帶檢查碼對上——條碼選型那篇管的是這一層;口徑定了再落一張圖,交給條碼產生器;回到解碼頁要核對的,是螢幕上那串與剪貼簿裡那串是不是同一個東西。

九、常被問反的三件事

Q:產生 13 位沒報錯,檢查碼就是對的?

不是。生成器第 184–194 行只在 length === 12 的分支裡算,13 位直接落空。要自證,把末位去掉、按 12 位重跑一遍再比。

Q:UPC-A 前面加個 0 就行了?

加 0 只在 11 位資料上成立;已經帶末位的 12 位不能再當作 EAN-13 的 12 位資料用,末位會被重算。

Q:面板寫著不支援,複製卻吐出一串碼?

那是上一次的舊值,兩個失敗出口都不清 dataset.result。看到失敗面板時,卡片內容不可信,剪貼簿裡那串更不可信。

📎 相關文章

文本合併後行數對不上:缺尾末換行、被吃掉的 BOM 和不可靠的順序 2026-10-26 Excel 貼上轉 JSON:日期、長數字與精度在貼上那一刻就已經變了 2026-10-26 二維碼解碼實測:糾錯等級救不了遮擋,每模組 2 像素才是生死線 2026-10-26 字頻還是詞頻:同一句中文,一份 23 項榜單和一份空表 2026-10-26 CSS 壓縮沒吃掉 calc 的空格,美化卻把 .btn:hover 劈成了兩截 2026-10-26