原理參考 Error Correction Coding - QR Code Tutorial html
首先介紹伽羅瓦域,接着使用日誌和反日誌來簡化GF(256)中的乘法。
由於糾錯算法中須要使用多項式進行運算,須要處理多項式的四則運算。
生成糾錯編碼要進行多項式長除法,使用兩種多項式,一個是消息多項式 (message polynomial),另外一個是生成器多項式 (generator polynomial)。消息多項式將被一個生成器多項式除;生成器多項式是經過相乘獲得的多項式 \((x - α^0) ... (x - α^{n-1})\) 。java
接着生成糾錯碼,用消息多項式除以生成器多項式獲得餘數多項式,它的係數就是所要的糾錯碼 。算法
伽羅瓦域,該域本質上是一組受限的數字(也稱有限域),以及一些建立仍在該集合中的數字的數學運算。數組
二維碼標準要求使用模2位運算和模100011101位運算。這意味着使用Galois Field \(2^8\),或者說 Galois Field 256,有時寫成GF(256)。 GF(\(2^w\)) 表示含有 \(2^w\)個元素的有限域。測試
GF(256)中的數字都在0到255(包括)的範圍內。注意,這是能夠用8位字節表示的數字的相同範圍(最大的8位字節是11111111,它等於255)。編碼
伽羅瓦域運算spa
GF(256) 包含從0到255的全部數字。GF(256) 中的數學運算本質上是循環的,這意味着若是在 GF(256) 中執行的數學運算的結果大於255,那麼就須要使用模運算來得到仍然在 GF 中的數字 。日誌
在伽羅瓦域中,負數與正數具備相同的值,所以 -n = n。這意味着在 GF 中的加法和減法是同樣的。伽羅瓦域中的加法和減法是經過正常的加減運算,而後再進行模運算來實現的。因爲咱們使用的是位模2算法(如二維碼規範中所述),因此這與執行XOR(異或^)操做是同樣的。例如:code
1 + 1 = 2 % 2 = 0 等價於 1 ^ 1 = 0orm
0 + 1 = 1 % 2 = 1 等價於 0 ^ 1 = 1
爲了對二維碼進行編碼,GF(256)中的全部加減法都是經過將兩個數字進行異或操做來進行的。
使用字節模100011101生成2的冪
GF(256) 中的全部數字自己都必須在0到255的範圍內,所以\(2^8\) 對於 GF 來講彷佛太大了,由於它等於256。
二維碼規範要求使用字節模 100011101 算法(其中 100011101 是一個二進制數,至關於十進制數的 285)。這意味着當一個數字是256或更大時,它應該與285異或操做。即:\(2^8\) = 256 ^ 285 = 29 。 當\(2^9\)時,須要使用\(2^8\)的值來運算:\(2^9\) = \(2^8\) * 2 = 29 * 2 = 58。
(使用此過程,GF(256)中的全部數字均可以用2n表示,其中n是0 <= n <= 255範圍內的數字。使用字節模100011101能夠確保全部的值都在0到255的範圍內。 )
下一小節將生成全部的 \(2^0\) ~ \(2^{255}\)的值來簡化 GF(256) 中的乘法。
在GF(256)中執行乘法只須要生成2的全部乘方, 以及相應的逆對數。
一個數的逆對數(antilogarithm),是指一個正數的對數即等於已知數。也就是說,在b=logaN對數運算中(以a爲底的對數),逆對數是已知對數b去尋求相應的真數N。
import java.util.Arrays; /** * 冪和逆對數 */ public class Power { /** * 生成2的n次方,GF(256) 範圍內的冪 */ public static int[] powersOf2ForGF(){ int gf[] = new int[256]; // key 是指數, value 是冪值 int t = 0; // 初始化第一個值 2**0=1 gf[0] = 1; for(int exponent=1; exponent<=255; exponent++){ gf[exponent] = exponent; t = gf[exponent-1]*2; if(t>255){ t ^= 285; } gf[exponent] = t; } return gf; } /** * 逆對數 anti log * @param log * @return */ public static int[] antiLog(int powers[]){ int antiLog[] = new int[256]; // key 是值, value 是指數 for(int exponent=0; exponent<=255; exponent++){ antiLog[powers[exponent]] = exponent; } antiLog[0] = Integer.MIN_VALUE; // 2**x=0,指數x不存在,無窮小則趨於0 antiLog[1] = 0; // 值爲1 對應的指數爲 0 和 255, 取0 return antiLog; } public static void main(String[] args){ // 2 的冪 int powers[] = Power.powersOf2ForGF(); System.out.println(Arrays.toString(powers)); // 生成逆對數 int antiLog[] = Power.antiLog(powers); System.out.println(Arrays.toString(antiLog)); } }
[1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, ... 216, 173, 71, 142, 1] [-2147483648, 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, ... 244, 234, 168, 80, 88, 175]
由於這些值不會變,因此能夠用數組保存起來,直接使用便可。
/** * 獲取一個key的冪 * @param k * @return */ public static int getPower(int k){ int powers[] = {1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, 76, 152, 45, 90, 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192, 157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, 193, 159, 35, 70, 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, 185, 111, 222, 161, 95, 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, 253, 231, 211, 187, 107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226, 217, 175, 67, 134, 17, 34, 68, 136, 13, 26, 52, 104, 208, 189, 103, 206, 129, 31, 62, 124, 248, 237, 199, 147, 59, 118, 236, 197, 151, 51, 102, 204, 133, 23, 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, 132, 21, 42, 84, 168, 77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, 228, 213, 183, 115, 230, 209, 191, 99, 198, 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255, 227, 219, 171, 75, 150, 49, 98, 196, 149, 55, 110, 220, 165, 87, 174, 65, 130, 25, 50, 100, 200, 141, 7, 14, 28, 56, 112, 224, 221, 167, 83, 166, 81, 162, 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, 172, 69, 138, 9, 18, 36, 72, 144, 61, 122, 244, 245, 247, 243, 251, 235, 203, 139, 11, 22, 44, 88, 176, 125, 250, 233, 207, 131, 27, 54, 108, 216, 173, 71, 142, 1}; return powers[k]; } /** * 獲取key的逆log * @param k * @return */ public static int getAntilog(int k){ // antiLog[0] 不存在,負無窮 int antiLog[] = {-2147483648, 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, 4, 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113, 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69, 29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, 6, 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, 30, 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61, 202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87, 7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, 227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, 55, 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, 242, 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162, 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246, 108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, 203, 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175}; return antiLog[k]; }
加法部分思路參考 多項式加法,用單鏈表實現
爲何說是普通多項式呢?由於GF(256) 的四則運算和算術上的四則運算是不一樣的。先來看看普通的多項式四則運算,具體實現略去(以後專門寫一篇多項式的文章),這裏看看運算的測試結果:
public static void main(String[] args){ Polynomial a = new Polynomial(); a.addItem(-1,2).addItem(3,2).addItem(0,1).addItem(90.5,3); a.addItem(10,2); a.addItem(7,0); System.out.println(a); // +90.5x^{3}+12x^{2}+7 Polynomial b = new Polynomial(); b.addItem(0,3).addItem(3,2).addItem(5,1).addItem(3,-3); System.out.println(b); // +3x^{2}+5x+3x^{-3} // 加法操做 Polynomial c = a.add(b); System.out.println(c); // +90.5x^{3}+15x^{2}+5x+7+3x^{-3} // 減法操做 Polynomial c2 = a.sub(b); System.out.println(c2); // +90.5x^{3}+9.0x^{2}-5.0x+7-3.0x^{-3} // 乘法操做 Polynomial d = a.mul(b); System.out.println(d); // +271.5x^{5.0}+488.5x^{4.0}+60.0x^{3.0}+21.0x^{2.0}+35.0x+271.5+36.0x^{-1.0}+21.0x^{-3.0} // 除法操做 Polynomial d1 = new Polynomial().addItem(1,3).addItem(5,2).addItem(6,1).addItem(3,0); Polynomial e = new Polynomial(new Node(1,1)).addItem(1,0); // 商 和 餘數 Polynomial quotient = new Polynomial(); Polynomial remainder = new Polynomial(); d1.div(e, quotient, remainder); System.out.println(d1); // x^{3}+5x^{2}+6x+3 System.out.println(e); // x+1 System.out.println(String.format("%s ... (%s) ", quotient, remainder)); // +x^{2.0}+4.0x+2.0 ... (+1.0) System.out.println(String.format("%s + [(%s) / (%s)]", quotient, remainder, e)); // +x^{2.0}+4.0x+2.0 + [(+1.0) / (+x+1)] }
兩個數的和差積分別爲:
a = \(+90.5x^{3}+12.0x^{2}+7\)
b = \(+3x^{2}+5x+3x^{-3}\)
a+b = \(+90.5x^{3}+15.0x^{2}+5x+7+3x^{-3}\)
a-b = \(+90.5x^{3}+9.0x^{2}-5.0x+7-3.0x^{-3}\)
a*b = \(+271.5x^{5.0}+488.5x^{4.0}+60.0x^{3.0}+21.0x^{2.0}+35.0x+271.5+36.0x^{-1.0}+21.0x^{-3.0}\)
除法用簡單的多項式測試:
被除數:a = \(+x^{3}+5x^{2}+6x+3\)
除數:b = \(+x+1\)
商爲:a/b = \(+x^{2.0}+4.0x+2.0\) 餘數是 \(1.0\)
表示爲:
$ +x^{2.0}+4.0x+2.0 ... (+1.0)\( 或者 \)+x^{2.0}+4.0x+2.0 + [(+1.0) / (+x+1)]$
爲何又是三則運算呢,由於除法比加減乘難一點,在下一節多項式生成好以後,下下一節再測試除法。(加減其實同樣,由於 -n = n, 因此減就是加,都是執行異或操做。)
public static void main(String[] args){ PolynomialGF a = new PolynomialGF(); a.addItem(3,2).addItem(0,1).addItem(90,3); a.addItem(10,2); a.addItem(7,0); System.out.println(a); // +2^{90}x^{3}+2^{115}x^{2}+x+2^{7} PolynomialGF b = new PolynomialGF(); b.addItem(0,3).addItem(3,2).addItem(5,1).addItem(3,-3); System.out.println(b); // +x^{3}+2^{3}x^{2}+2^{5}x // 加法操做 PolynomialGF c = a.add(b); System.out.println(c); // +2^{62}x^{3}+2^{10}x^{2}+2^{138}x+2^{7} // 乘法操做 PolynomialGF d = a.mul(b); System.out.println(d); // +2^{90}x^{6}+2^{73}x^{5}+2^{225}x^{4}+2^{171}x^{3}+2^{143}x^{2}+2^{12}x }
消息多項式使用來自數據編碼步驟的數據碼字做爲其係數。例如,若是轉換爲整數的數據碼字是2五、218和35,則消息多項式將是\(25x^2 + 218x + 35\)
生成消息多項式代碼:
public class xxx{ public static void main(String[] args){ // 消息多項式 int[] code = {2, 25, 218, 35}; Polynomial mP = new ErrorCorrectionCoding().messagePolynomial(code); System.out.println(mP); // 2x^{3}+25x^{2}+218x+35 } /** * 消息多項式 * @param code * @return */ public Polynomial messagePolynomial(int[] code){ Polynomial m = new Polynomial(); for(int i=0; i < code.length; i++){ m.addItem(code[i], code.length-i-1); } return m; } }
使用數字碼 2, 25, 218, 35
生成消息多項式爲 \(2x^{3}+25x^{2}+218x+35\)。
生成器多項式是經過相乘獲得的多項式: \((x - α^0) ... (x - α^{n-1})\) 。
public class xxx{ public static void main(String[] args){ // 生成器多項式 (2個糾錯碼) PolynomialGF gp = new ErrorCorrectionCoding().generatorPolynomial(2); System.out.println(gp); // +x^{2}+α^{25}x+α^{1} } /** * (n個錯誤糾正碼的)生成器多項式 (x - α^0) ... (x - α^{n-1}) * 遞歸實現,上一版本是普通實現 * @param ECC_Number 糾錯碼字數(number of error correction codewords) * @return */ public PolynomialGF generatorPolynomial(int ECC_Number){ if(ECC_Number<1){ return null; } PolynomialGF t = new PolynomialGF(); // n=0 if(ECC_Number==1){ t.addItem(0, 1).addItem(0,0); return t; }else{ t = new PolynomialGF().addItem(0, 1).addItem(ECC_Number-1,0); return t.mul(generatorPolynomial(ECC_Number-1)); } } }
public static void main(String[] args){ // 糾錯碼數 int e_n = 10; // 消息多項式 int[] code = {32, 91, 11, 120, 209, 114, 220, 77, 67, 64, 236, 17, 236, 17, 236, 17}; PolynomialGF mp = new ErrorCorrectionCoding().messagePolynomial(code); System.out.println(mp); // +α^{5}x^{15}+α^{92}x^{14}+α^{238}x^{13}+α^{78}x^{12}+α^{161}x^{11}+α^{155}x^{10}+α^{187}x^{9}+α^{145}x^{8}+α^{98}x^{7}+α^{6}x^{6}+α^{122}x^{5}+α^{100}x^{4}+α^{122}x^{3}+α^{100}x^{2}+α^{122}x+α^{100} // 生成器多項式 (10個糾錯碼) PolynomialGF gp = new ErrorCorrectionCoding().generatorPolynomial(e_n); System.out.println(gp); // +x^{10}+α^{251}x^{9}+α^{67}x^{8}+α^{46}x^{7}+α^{61}x^{6}+α^{118}x^{5}+α^{70}x^{4}+α^{64}x^{3}+α^{94}x^{2}+α^{32}x+α^{45} // 消息多項式 * x^n PolynomialGF xn = new PolynomialGF(new Node(0, e_n)); mp = mp.mul(xn); System.out.println(mp); // +α^{5}x^{25}+α^{92}x^{24}+α^{238}x^{23}+α^{78}x^{22}+α^{161}x^{21}+α^{155}x^{20}+α^{187}x^{19}+α^{145}x^{18}+α^{98}x^{17}+α^{6}x^{16}+α^{122}x^{15}+α^{100}x^{14}+α^{122}x^{13}+α^{100}x^{12}+α^{122}x^{11}+α^{100}x^{10} System.out.println("多項式除法,商和餘數分別爲:"); PolynomialGF quotient = new PolynomialGF(), remainder = new PolynomialGF(); mp.div(gp, quotient, remainder); Coef.outputFormatValue = true; // 設置係數的輸出格式 System.out.println(quotient); // +32x^{15}+89x^{14}+61x^{13}+138x^{12}+243x^{11}+149x^{10}+135x^{9}+183x^{8}+59x^{7}+184x^{6}+51x^{5}+41x^{4}+179x^{3}+70x^{2}+84x+107 System.out.println(remainder); // +196x^{9}+35x^{8}+39x^{7}+119x^{6}+235x^{5}+215x^{4}+231x^{3}+226x^{2}+93x+23 System.out.println("糾錯碼爲:"); int[] e = remainder.getCoefs(); System.out.println(Arrays.toString(e)); // [196, 35, 39, 119, 235, 215, 231, 226, 93, 23] }
首先準備消息多項式和生成器多項式
消息多項式應該是:
\(+32x^{15}+91x^{14}+11x^{13}+120x^{12}+209x^{11}+114x^{10}+220x^{9}+77x^{8}+67x^{7}+64x^{6}+236x^{5}+17x^{4}+236x^{3}+17x^{2}+236x+17\)
爲了統一格式,將每一項的係數是數值轉成了 \(α^n\) (α=2) 的格式:
\(+α^{5}x^{15}+α^{92}x^{14}+α^{238}x^{13}+α^{78}x^{12}+α^{161}x^{11}+α^{155}x^{10}+α^{187}x^{9}+α^{145}x^{8}+α^{98}x^{7}+α^{6}x^{6}+α^{122}x^{5}+α^{100}x^{4}+α^{122}x^{3}+α^{100}x^{2}+α^{122}x+α^{100}\)
爲了確保前導項的指數在除法期間不會變得過小,將消息多項式乘以 \(x^n\),其中n是所需的錯誤糾正碼的數量。在這種狀況下,n是10,對於10個糾錯碼,將消息多項式乘以x10,獲得
\(+α^{5}x^{25}+α^{92}x^{24}+α^{238}x^{23}+α^{78}x^{22}+α^{161}x^{21}+α^{155}x^{20}+α^{187}x^{19}+α^{145}x^{18}+α^{98}x^{17}+α^{6}x^{16}+α^{122}x^{15}+α^{100}x^{14}+α^{122}x^{13}+α^{100}x^{12}+α^{122}x^{11}+α^{100}x^{10}\)
生成器多項式(糾錯碼數爲10)是:
\(+x^{10}+α^{251}x^{9}+α^{216}x^{8}+α^{78}x^{7}+α^{69}x^{6}+α^{76}x^{5}+α^{114}x^{4}+α^{64}x^{3}+α^{94}x^{2}+α^{32}x+α^{45}\)
使用多項式除法計算出餘數
\(+196x^{9}+35x^{8}+39x^{7}+119x^{6}+235x^{5}+215x^{4}+231x^{3}+226x^{2}+93x+23\)
餘數的係數就是要生成的糾錯碼:
[196, 35, 39, 119, 235, 215, 231, 226, 93, 23]
/** * 生成糾錯碼 * @param dataCode data codewords 數據碼 * @param eNum error correction codewords 糾錯碼個數 * @return */ public static int[] getCode(int dataCode[], int eNum){ if(eNum<1 || dataCode.length<1){ return null; } // 消息多項式 PolynomialGF mp = new ErrorCorrectionCoding().messagePolynomial(dataCode); // 生成器多項式 (nNum個糾錯碼) PolynomialGF gp = new ErrorCorrectionCoding().generatorPolynomial(eNum); // 消息多項式 * x^n PolynomialGF xn = new PolynomialGF(new Node(0, eNum)); mp = mp.mul(xn); // 多項式除法,求餘數"); PolynomialGF quotient = new PolynomialGF(); PolynomialGF remainder = new PolynomialGF(); mp.div(gp, quotient, remainder); // 糾錯碼 int[] e = remainder.getCoefs(); return e; } --------------------- 封裝後調用: // 數據碼 和 糾錯碼數 int [] codeWords = {32, 91, 11, 120, 209, 114, 220, 77, 67, 64, 236, 17, 236, 17, 236, 17}; int e_n = 10; getCode(codeWords, e_n))
bingo!