From 768c9c083e9f6e90a70f800bb3223662d9bfc813 Mon Sep 17 00:00:00 2001 From: Kris Vandermotten Date: Mon, 17 Aug 2026 21:02:47 +0200 Subject: [PATCH 1/2] Optimize CalculateGeneratorPolynom --- QRCoder/QRCodeGenerator.cs | 207 ++----------------------- QRCoder/QRCodeGenerator/ECCInfo.cs | 6 +- QRCoder/QRCodeGenerator/GaloisField.cs | 34 +--- QRCoder/QRCodeGenerator/Polynom.cs | 159 ++++++++++--------- QRCoder/QRCodeGenerator/PolynomItem.cs | 5 +- QRCoderTests/QRGeneratorTests.cs | 76 ++++++++- 6 files changed, 178 insertions(+), 309 deletions(-) diff --git a/QRCoder/QRCodeGenerator.cs b/QRCoder/QRCodeGenerator.cs index a0c02dc7..2a9a0df0 100644 --- a/QRCoder/QRCodeGenerator.cs +++ b/QRCoder/QRCodeGenerator.cs @@ -1,10 +1,7 @@ #if HAS_SPAN using System.Buffers; #endif -using System; -using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Text; namespace QRCoder; @@ -368,7 +365,7 @@ List CalculateECCBlocks() { List codewordBlocks; // Generate the generator polynomial using the number of ECC words. - using (var generatorPolynom = CalculateGeneratorPolynom(eccInfo.ECCPerBlock)) + using (var generatorPolynom = Polynom.CreateGeneratorPolynom(eccInfo.ECCPerBlock)) { //Calculate error correction words codewordBlocks = CodewordBlock.GetList(eccInfo.BlocksInGroup1 + eccInfo.BlocksInGroup2); @@ -699,7 +696,7 @@ private static ArraySegment CalculateECCWords(BitArray bitArray, int offse // Convert the first coefficient to its corresponding alpha exponent unless it's zero. // Coefficients that are zero remain zero because log(0) is undefined. var index0Coefficient = leadTermSource[0].Coefficient; - index0Coefficient = index0Coefficient == 0 ? 0 : GaloisField.GetAlphaExpFromIntVal(index0Coefficient); + index0Coefficient = GaloisField.GetAlphaExpFromIntVal((byte)index0Coefficient); var alphaNotation = new PolynomItem(index0Coefficient, leadTermSource[0].Exponent); var resPoly = MultiplyGeneratorPolynomByLeadterm(generatorPolynom, alphaNotation, i); ConvertToDecNotationInPlace(resPoly); @@ -742,7 +739,7 @@ private static void ConvertToDecNotationInPlace(Polynom poly) for (var i = 0; i < poly.Count; i++) { // Convert the alpha exponent of the coefficient to its decimal value and create a new polynomial item with the updated coefficient. - poly[i] = new PolynomItem(GaloisField.GetIntValFromAlphaExp(poly[i].Coefficient), poly[i].Exponent); + poly[i] = new PolynomItem(GaloisField.GetIntValFromAlphaExp((byte)poly[i].Coefficient), poly[i].Exponent); } } @@ -816,55 +813,25 @@ private static Polynom CalculateMessagePolynom(BitArray bitArray, int offset, in // Convert each 8-bit segment into a decimal value and add it to the polynomial for (int i = 0; i < polynomLength; i++) { - messagePol.Add(new PolynomItem(BinToDec(bitArray, offset, 8), exponent--)); + messagePol.Add(new PolynomItem(BinToDec(bitArray, offset), exponent--)); offset += 8; } return messagePol; } - /// - /// Calculates the generator polynomial used for creating error correction codewords. - /// - /// The number of error correction codewords to generate. - /// A polynomial that can be used to generate ECC codewords. - private static Polynom CalculateGeneratorPolynom(int numEccWords) - { - var generatorPolynom = new Polynom(2); // Start with the simplest form of the polynomial - generatorPolynom.Add(new PolynomItem(0, 1)); - generatorPolynom.Add(new PolynomItem(0, 0)); - - using (var multiplierPolynom = new Polynom(numEccWords * 2)) // Used for polynomial multiplication - { - for (var i = 1; i <= numEccWords - 1; i++) - { - // Clear and set up the multiplier polynomial for the current multiplication - multiplierPolynom.Clear(); - multiplierPolynom.Add(new PolynomItem(0, 1)); - multiplierPolynom.Add(new PolynomItem(i, 0)); - - // Multiply the generator polynomial by the current multiplier polynomial - var newGeneratorPolynom = MultiplyAlphaPolynoms(generatorPolynom, multiplierPolynom); - generatorPolynom.Dispose(); - generatorPolynom = newGeneratorPolynom; - } - } - - return generatorPolynom; // Return the completed generator polynomial - } - /// /// Converts a segment of a BitArray into its decimal (integer) equivalent. /// /// The integer value that represents the specified binary data. - private static int BinToDec(BitArray bitArray, int offset, int count) + private static byte BinToDec(BitArray bitArray, int offset) { var ret = 0; - for (int i = 0; i < count; i++) + for (int i = 0; i < 8; i++) { - ret ^= bitArray[offset + i] ? 1 << (count - i - 1) : 0; + ret ^= bitArray[offset + i] ? 1 << (7 - i) : 0; } - return ret; + return (byte)ret; } /// @@ -1083,8 +1050,7 @@ private static Polynom XORPolynoms(Polynom messagePolynom, Polynom resPolynom) for (var i = 1; i < longPoly.Count; i++) { var polItemRes = new PolynomItem( - longPoly[i].Coefficient ^ - (shortPoly.Count > i ? shortPoly[i].Coefficient : 0), + longPoly[i].Coefficient ^ (shortPoly.Count > i ? shortPoly[i].Coefficient : 0), messagePolynom[0].Exponent - i ); resultPolynom.Add(polItemRes); @@ -1103,7 +1069,6 @@ private static Polynom MultiplyGeneratorPolynomByLeadterm(Polynom genPolynom, Po foreach (var polItemBase in genPolynom) { var polItemRes = new PolynomItem( - (polItemBase.Coefficient + leadTerm.Coefficient) % 255, polItemBase.Exponent - lowerExponentBy ); @@ -1112,160 +1077,6 @@ private static Polynom MultiplyGeneratorPolynomByLeadterm(Polynom genPolynom, Po return resultPolynom; } - /// - /// Multiplies two polynomials, treating coefficients as exponents of a primitive element (alpha), which is common in error correction algorithms such as Reed-Solomon. - /// - /// The first polynomial to multiply. - /// The second polynomial to multiply. - /// A new polynomial which is the result of the multiplication of the two input polynomials. - private static Polynom MultiplyAlphaPolynoms(Polynom polynomBase, Polynom polynomMultiplier) - { - // Initialize a new polynomial with a size based on the product of the sizes of the two input polynomials. - var resultPolynom = new Polynom(polynomMultiplier.Count * polynomBase.Count); - - // Multiply each term of the first polynomial by each term of the second polynomial. - foreach (var polItemBase in polynomMultiplier) - { - foreach (var polItemMulti in polynomBase) - { - // Create a new polynomial term with the coefficients added (as exponents) and exponents summed. - var polItemRes = new PolynomItem - ( - GaloisField.ShrinkAlphaExp(polItemBase.Coefficient + polItemMulti.Coefficient), - (polItemBase.Exponent + polItemMulti.Exponent) - ); - resultPolynom.Add(polItemRes); - } - } - - // Identify and merge terms with the same exponent. -#if NET5_0_OR_GREATER - var toGlue = GetNotUniqueExponents(resultPolynom, resultPolynom.Count <= 128 ? stackalloc int[128].Slice(0, resultPolynom.Count) : new int[resultPolynom.Count]); - var gluedPolynoms = toGlue.Length <= 128 - ? stackalloc PolynomItem[128].Slice(0, toGlue.Length) - : new PolynomItem[toGlue.Length]; -#else - var toGlue = GetNotUniqueExponents(resultPolynom); - var gluedPolynoms = new PolynomItem[toGlue.Length]; -#endif - var gluedPolynomsIndex = 0; - foreach (var exponent in toGlue) - { - var coefficient = 0; - foreach (var polynomOld in resultPolynom) - { - if (polynomOld.Exponent == exponent) - coefficient ^= GaloisField.GetIntValFromAlphaExp(polynomOld.Coefficient); - } - - // Fix the polynomial terms by recalculating the coefficients based on XORed results. - var polynomFixed = new PolynomItem(GaloisField.GetAlphaExpFromIntVal(coefficient), exponent); - gluedPolynoms[gluedPolynomsIndex++] = polynomFixed; - } - - // Remove duplicated exponents and add the corrected ones back. - for (int i = resultPolynom.Count - 1; i >= 0; i--) -#if NET5_0_OR_GREATER - if (toGlue.Contains(resultPolynom[i].Exponent)) -#else - if (Array.IndexOf(toGlue, resultPolynom[i].Exponent) >= 0) -#endif - resultPolynom.RemoveAt(i); - foreach (var polynom in gluedPolynoms) - resultPolynom.Add(polynom); - - // Sort the polynomial terms by exponent in descending order. - resultPolynom.Sort((x, y) => -x.Exponent.CompareTo(y.Exponent)); - return resultPolynom; - - // Auxiliary function to identify exponents that appear more than once in the polynomial. -#if NET5_0_OR_GREATER - static ReadOnlySpan GetNotUniqueExponents(Polynom list, Span buffer) - { - // It works as follows: - // 1. a scratch buffer of the same size as the list is passed in - // 2. exponents are written / copied to that scratch buffer - // 3. scratch buffer is sorted, thus the exponents are in order - // 4. for each item in the scratch buffer (= ordered exponents) it's compared w/ the previous one - // * if equal, then increment a counter - // * else check if the counter is $>0$ and if so write the exponent to the result - // - // For writing the result the same scratch buffer is used, as by definition the index to write the result - // is `<=` the iteration index, so no overlap, etc. can occur. - - Debug.Assert(list.Count == buffer.Length); - - int idx = 0; - foreach (var row in list) - { - buffer[idx++] = row.Exponent; - } - - buffer.Sort(); - - idx = 0; - int expCount = 0; - int last = buffer[0]; - - for (int i = 1; i < buffer.Length; ++i) - { - if (buffer[i] == last) - { - expCount++; - } - else - { - if (expCount > 0) - { - Debug.Assert(idx <= i - 1); - - buffer[idx++] = last; - expCount = 0; - } - } - - last = buffer[i]; - } - - return buffer.Slice(0, idx); - } -#else - static int[] GetNotUniqueExponents(Polynom list) - { - var dic = new Dictionary(list.Count); - foreach (var row in list) - { -#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER - if (!dic.TryAdd(row.Exponent, false)) -#else - if (!dic.ContainsKey(row.Exponent)) - dic.Add(row.Exponent, false); - else -#endif - dic[row.Exponent] = true; - } - - // Collect all exponents that appeared more than once. - int count = 0; - foreach (var row in dic) - { - if (row.Value) - count++; - } - - var result = new int[count]; - int i = 0; - foreach (var row in dic) - { - if (row.Value) - result[i++] = row.Key; - } - - return result; - } -#endif - } - /// public virtual void Dispose() { diff --git a/QRCoder/QRCodeGenerator/ECCInfo.cs b/QRCoder/QRCodeGenerator/ECCInfo.cs index 9e8b787e..2af53b73 100644 --- a/QRCoder/QRCodeGenerator/ECCInfo.cs +++ b/QRCoder/QRCodeGenerator/ECCInfo.cs @@ -1,3 +1,5 @@ +using System.Diagnostics; + namespace QRCoder; public partial class QRCodeGenerator @@ -5,7 +7,7 @@ public partial class QRCodeGenerator /// /// Represents the error correction coding (ECC) information for a specific version and error correction level of a QR code. /// - private struct ECCInfo + private readonly struct ECCInfo { /// /// Initializes a new instance of the ECCInfo struct with specified properties. @@ -21,6 +23,7 @@ private struct ECCInfo public ECCInfo(int version, ECCLevel errorCorrectionLevel, int totalDataCodewords, int eccPerBlock, int blocksInGroup1, int codewordsInGroup1, int blocksInGroup2, int codewordsInGroup2) { + Debug.Assert(eccPerBlock < 32); Version = version; ErrorCorrectionLevel = errorCorrectionLevel; TotalDataCodewords = totalDataCodewords; @@ -42,6 +45,7 @@ public ECCInfo(int version, ECCLevel errorCorrectionLevel, int totalDataCodeword /// The number of error correction codewords per block. public ECCInfo(int version, ECCLevel errorCorrectionLevel, int totalDataCodewords, int totalDataBits, int eccPerBlock) { + Debug.Assert(eccPerBlock < 32); Version = version; ErrorCorrectionLevel = errorCorrectionLevel; TotalDataCodewords = totalDataCodewords; diff --git a/QRCoder/QRCodeGenerator/GaloisField.cs b/QRCoder/QRCodeGenerator/GaloisField.cs index 46c8f262..27fd4272 100644 --- a/QRCoder/QRCodeGenerator/GaloisField.cs +++ b/QRCoder/QRCodeGenerator/GaloisField.cs @@ -1,5 +1,3 @@ -using System.Diagnostics; - namespace QRCoder; public partial class QRCodeGenerator @@ -16,16 +14,16 @@ public partial class QRCodeGenerator internal static class GaloisField { #if HAS_SPAN - internal static ReadOnlySpan _galoisFieldByExponentAlpha => + internal static ReadOnlySpan _integerValueByAlphaExponent => #else - internal static readonly byte[] _galoisFieldByExponentAlpha = + internal static readonly byte[] _integerValueByAlphaExponent = #endif [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]; #if HAS_SPAN - internal static ReadOnlySpan _galoisFieldByIntegerValue => + internal static ReadOnlySpan _alphaExponentByIntegerValue => #else - internal static readonly byte[] _galoisFieldByIntegerValue = + internal static readonly byte[] _alphaExponentByIntegerValue = #endif [0, 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]; @@ -33,30 +31,14 @@ internal static class GaloisField /// Retrieves the integer value from the Galois field that corresponds to a given exponent. /// This is used in Reed-Solomon and other error correction calculations involving Galois fields. /// - public static int GetIntValFromAlphaExp(int exp) - => _galoisFieldByExponentAlpha[exp]; + public static byte GetIntValFromAlphaExp(byte exp) + => _integerValueByAlphaExponent[exp]; /// /// Retrieves the exponent from the Galois field that corresponds to a given integer value. /// Throws an exception if the integer value is zero, as zero does not have a logarithmic representation in the field. /// - public static int GetAlphaExpFromIntVal(int intVal) - { - if (intVal == 0) - ThrowIntValOutOfRangeException(); // Zero is not valid as it does not have an exponent representation. - return _galoisFieldByIntegerValue[intVal]; - - void ThrowIntValOutOfRangeException() => throw new ArgumentOutOfRangeException(nameof(intVal), "The provided integer value is out of range, as zero is not representable."); - } - - /// - /// Normalizes a Galois field exponent to ensure it remains within the bounds of the field's size. - /// This is particularly necessary when performing multiplications in the field which can result in exponents exceeding the field's maximum. - /// - public static int ShrinkAlphaExp(int alphaExp) - { - Debug.Assert(alphaExp >= 0); - return (int)((uint)alphaExp % 256 + (uint)alphaExp / 256); - } + public static byte GetAlphaExpFromIntVal(byte intVal) + => _alphaExponentByIntegerValue[intVal]; } } diff --git a/QRCoder/QRCodeGenerator/Polynom.cs b/QRCoder/QRCodeGenerator/Polynom.cs index 90237945..6713fe9c 100644 --- a/QRCoder/QRCodeGenerator/Polynom.cs +++ b/QRCoder/QRCodeGenerator/Polynom.cs @@ -7,8 +7,80 @@ public partial class QRCodeGenerator /// /// Represents a polynomial, which is a sum of polynomial terms. /// - private struct Polynom : IDisposable + internal struct Polynom : IDisposable { +#if HAS_SPAN + private static ReadOnlySpan _generatorPolynomCoefficients => +#else + private static readonly byte[] _generatorPolynomCoefficients = +#endif + [ + 0, + 1, 25, + 3, 199, 198, + 6, 78, 249, 75, + 10, 119, 166, 164, 113, + 15, 176, 5, 134, 0, 166, + 21, 102, 238, 149, 146, 229, 87, + 28, 196, 252, 215, 249, 208, 238, 175, + 36, 123, 11, 149, 235, 231, 137, 246, 95, + 45, 32, 94, 64, 70, 118, 61, 46, 67, 251, + 55, 10, 227, 116, 209, 177, 172, 194, 91, 192, 220, + 66, 157, 87, 131, 143, 198, 113, 187, 121, 98, 43, 102, + 78, 140, 206, 218, 130, 104, 106, 100, 86, 100, 176, 152, 74, + 91, 22, 59, 207, 87, 216, 137, 218, 124, 190, 48, 155, 249, 199, + 105, 99, 5, 124, 140, 237, 58, 58, 51, 37, 202, 91, 61, 183, 8, + 120, 225, 194, 182, 169, 147, 191, 91, 3, 76, 161, 102, 109, 107, 104, 120, + 136, 163, 243, 39, 150, 99, 24, 147, 214, 206, 123, 239, 43, 78, 206, 139, 43, + 153, 96, 98, 5, 179, 252, 148, 152, 187, 79, 170, 118, 97, 184, 94, 158, 234, 215, + 171, 220, 138, 222, 252, 133, 153, 128, 44, 159, 150, 17, 83, 90, 52, 153, 105, 3, 67, + 190, 188, 212, 212, 164, 156, 239, 83, 225, 221, 180, 202, 187, 26, 163, 61, 50, 79, 60, 17, + 210, 175, 148, 254, 122, 36, 230, 137, 148, 115, 210, 200, 85, 98, 67, 140, 181, 247, 104, 233, 240, + 231, 165, 105, 160, 134, 219, 80, 98, 172, 8, 74, 200, 53, 221, 109, 14, 230, 93, 242, 247, 171, 210, + 253, 147, 56, 78, 1, 192, 224, 164, 94, 248, 183, 25, 14, 150, 193, 17, 65, 103, 49, 91, 146, 102, 171, + 21, 227, 96, 87, 232, 117, 0, 111, 218, 228, 226, 192, 152, 169, 180, 159, 126, 251, 117, 211, 48, 135, 121, 229, + 45, 252, 178, 129, 243, 95, 182, 144, 167, 99, 208, 237, 66, 54, 201, 148, 15, 59, 12, 26, 170, 39, 156, 181, 231, + 70, 218, 145, 153, 227, 48, 102, 13, 142, 245, 21, 161, 53, 165, 28, 111, 201, 145, 17, 118, 182, 103, 2, 158, 125, 173, + 96, 149, 17, 26, 157, 193, 216, 94, 172, 126, 73, 135, 138, 58, 45, 99, 70, 237, 9, 29, 180, 21, 227, 165, 8, 228, 79, + 123, 9, 37, 242, 119, 212, 195, 42, 87, 245, 43, 21, 201, 232, 27, 205, 147, 195, 190, 110, 180, 108, 234, 224, 104, 200, 223, 168, + 151, 24, 140, 250, 68, 162, 202, 9, 23, 148, 150, 234, 75, 28, 189, 175, 241, 5, 136, 24, 249, 96, 54, 219, 151, 29, 183, 45, 156, + 180, 192, 40, 238, 216, 251, 37, 156, 130, 224, 193, 226, 173, 42, 125, 222, 96, 239, 86, 110, 48, 50, 182, 179, 31, 216, 152, 145, 173, 41, + 210, 200, 187, 117, 183, 123, 105, 225, 1, 55, 248, 248, 144, 119, 118, 137, 122, 73, 44, 39, 113, 83, 115, 31, 225, 75, 63, 93, 252, 37, 20 + ]; + + /// + /// Creates the generator polynomial used for creating error correction codewords. + /// + /// The number of error correction codewords to generate. + /// A polynomial that can be used to generate ECC codewords. + public static Polynom CreateGeneratorPolynom(int numEccWords) + { + Debug.Assert(numEccWords < 32); + + int startIndex = (numEccWords - 1) * numEccWords / 2; + + var generatorPolynomial = new Polynom(numEccWords + 1); + + // Return the polynomial terms by exponent in descending order. + // The highest order coefficient is always 0. + generatorPolynomial.Add(new PolynomItem(0, numEccWords)); + +#if HAS_SPAN + var coefficients = _generatorPolynomCoefficients.Slice(startIndex, numEccWords); + for (int i = coefficients.Length - 1; i >= 0; i--) + { + generatorPolynomial.Add(new PolynomItem(coefficients[i], i)); + } +#else + for (int i = numEccWords - 1; i >= 0; i--) + { + generatorPolynomial.Add(new PolynomItem(_generatorPolynomCoefficients[startIndex + i], i)); + } +#endif + + return generatorPolynomial; + } + private PolynomItem[] _polyItems; /// @@ -35,8 +107,7 @@ public void Add(PolynomItem item) /// public void RemoveAt(int index) { - if ((uint)index >= (uint)Count) - ThrowIndexArgumentOutOfRangeException(); + Debug.Assert((uint)index < (uint)Count); if (index < Count - 1) Array.Copy(_polyItems, index + 1, _polyItems, index, Count - index - 1); @@ -51,23 +122,16 @@ public PolynomItem this[int index] { get { - if ((uint)index >= Count) - ThrowIndexArgumentOutOfRangeException(); + Debug.Assert((uint)index < Count); return _polyItems[index]; } set { - if ((uint)index >= Count) - ThrowIndexArgumentOutOfRangeException(); + Debug.Assert((uint)index < Count); _polyItems[index] = value; } } -#if NET6_0_OR_GREATER - [StackTraceHidden] -#endif - private static void ThrowIndexArgumentOutOfRangeException() => throw new ArgumentOutOfRangeException("index"); - /// /// Gets the number of polynomial terms in the polynomial. @@ -90,59 +154,6 @@ public Polynom Clone() return newPolynom; } - /// - /// Sorts the collection of using a custom comparer function. - /// - /// - /// A function that compares two objects and returns an integer indicating their relative order: - /// less than zero if the first is less than the second, zero if they are equal, or greater than zero if the first is greater than the second. - /// - public void Sort(Func comparer) - { - if (comparer == null) - throw new ArgumentNullException(nameof(comparer)); - - var items = _polyItems ?? throw new ObjectDisposedException(nameof(Polynom)); - - if (Count <= 1) - { - return; // Nothing to sort if the list is empty or contains only one element - } - - void QuickSort(int left, int right) - { - int i = left; - int j = right; - var pivot = items[(left + right) / 2]; - - while (i <= j) - { - while (comparer(items[i], pivot) < 0) - i++; - while (comparer(items[j], pivot) > 0) - j--; - - if (i <= j) - { - // Swap items[i] and items[j] - var temp = items[i]; - items[i] = items[j]; - items[j] = temp; - i++; - j--; - } - } - - // Recursively sort the sub-arrays - if (left < j) - QuickSort(left, j); - if (i < right) - QuickSort(i, right); - } - - QuickSort(0, Count - 1); - } - /// /// Returns a string that represents the polynomial in standard algebraic notation. /// Example output: "a^2*x^3 + a^5*x^1 + a^3*x^0", which represents the polynomial 2x³ + 5x + 3. @@ -176,22 +187,8 @@ public void Dispose() /// private void AssertCapacity(int min) { - if (_polyItems.Length < min) - { - // All math by QRCoder should be done with fixed polynomials, so we don't need to grow the capacity. - ThrowNotSupportedException(); - - // Sample code for growing the capacity: - //var newArray = RentArray(Math.Max(min - 1, 8) * 2); // Grow by 2x, but at least by 8 - //Array.Copy(_polyItems, newArray, _length); - //ReturnArray(_polyItems); - //_polyItems = newArray; - } - -#if NET6_0_OR_GREATER - [StackTraceHidden] -#endif - void ThrowNotSupportedException() => throw new NotSupportedException("The polynomial capacity is fixed and cannot be increased."); + // All math by QRCoder should be done with fixed polynomials, so we don't need to grow the capacity. + Debug.Assert(_polyItems.Length >= min); } #if HAS_SPAN diff --git a/QRCoder/QRCodeGenerator/PolynomItem.cs b/QRCoder/QRCodeGenerator/PolynomItem.cs index a881cae0..cca12d2e 100644 --- a/QRCoder/QRCodeGenerator/PolynomItem.cs +++ b/QRCoder/QRCodeGenerator/PolynomItem.cs @@ -1,3 +1,5 @@ +using System.Diagnostics; + namespace QRCoder; public partial class QRCodeGenerator @@ -6,7 +8,7 @@ public partial class QRCodeGenerator /// Represents an individual term of a polynomial, consisting of a coefficient and an exponent. /// For example, the term 3x² would be represented as a with a coefficient of 3 and an exponent of 2. /// - private struct PolynomItem + internal readonly struct PolynomItem { /// /// Initializes a new instance of the struct with the specified coefficient and exponent. @@ -15,6 +17,7 @@ private struct PolynomItem /// The exponent of the polynomial term. For example, in the term 3x², the exponent is 2. public PolynomItem(int coefficient, int exponent) { + Debug.Assert((uint)coefficient < 256); Coefficient = coefficient; Exponent = exponent; } diff --git a/QRCoderTests/QRGeneratorTests.cs b/QRCoderTests/QRGeneratorTests.cs index 3d771395..969d6947 100644 --- a/QRCoderTests/QRGeneratorTests.cs +++ b/QRCoderTests/QRGeneratorTests.cs @@ -10,7 +10,7 @@ public class QRGeneratorTests public void validate_antilogtable() { var checkString = string.Empty; - var gField = QRCodeGenerator.GaloisField._galoisFieldByExponentAlpha; + var gField = QRCodeGenerator.GaloisField._integerValueByAlphaExponent; gField.Length.ShouldBe(256); for (int i = 0; i < gField.Length; i++) { @@ -18,7 +18,7 @@ public void validate_antilogtable() } checkString.ShouldBe("0,1,:1,2,:2,4,:3,8,:4,16,:5,32,:6,64,:7,128,:8,29,:9,58,:10,116,:11,232,:12,205,:13,135,:14,19,:15,38,:16,76,:17,152,:18,45,:19,90,:20,180,:21,117,:22,234,:23,201,:24,143,:25,3,:26,6,:27,12,:28,24,:29,48,:30,96,:31,192,:32,157,:33,39,:34,78,:35,156,:36,37,:37,74,:38,148,:39,53,:40,106,:41,212,:42,181,:43,119,:44,238,:45,193,:46,159,:47,35,:48,70,:49,140,:50,5,:51,10,:52,20,:53,40,:54,80,:55,160,:56,93,:57,186,:58,105,:59,210,:60,185,:61,111,:62,222,:63,161,:64,95,:65,190,:66,97,:67,194,:68,153,:69,47,:70,94,:71,188,:72,101,:73,202,:74,137,:75,15,:76,30,:77,60,:78,120,:79,240,:80,253,:81,231,:82,211,:83,187,:84,107,:85,214,:86,177,:87,127,:88,254,:89,225,:90,223,:91,163,:92,91,:93,182,:94,113,:95,226,:96,217,:97,175,:98,67,:99,134,:100,17,:101,34,:102,68,:103,136,:104,13,:105,26,:106,52,:107,104,:108,208,:109,189,:110,103,:111,206,:112,129,:113,31,:114,62,:115,124,:116,248,:117,237,:118,199,:119,147,:120,59,:121,118,:122,236,:123,197,:124,151,:125,51,:126,102,:127,204,:128,133,:129,23,:130,46,:131,92,:132,184,:133,109,:134,218,:135,169,:136,79,:137,158,:138,33,:139,66,:140,132,:141,21,:142,42,:143,84,:144,168,:145,77,:146,154,:147,41,:148,82,:149,164,:150,85,:151,170,:152,73,:153,146,:154,57,:155,114,:156,228,:157,213,:158,183,:159,115,:160,230,:161,209,:162,191,:163,99,:164,198,:165,145,:166,63,:167,126,:168,252,:169,229,:170,215,:171,179,:172,123,:173,246,:174,241,:175,255,:176,227,:177,219,:178,171,:179,75,:180,150,:181,49,:182,98,:183,196,:184,149,:185,55,:186,110,:187,220,:188,165,:189,87,:190,174,:191,65,:192,130,:193,25,:194,50,:195,100,:196,200,:197,141,:198,7,:199,14,:200,28,:201,56,:202,112,:203,224,:204,221,:205,167,:206,83,:207,166,:208,81,:209,162,:210,89,:211,178,:212,121,:213,242,:214,249,:215,239,:216,195,:217,155,:218,43,:219,86,:220,172,:221,69,:222,138,:223,9,:224,18,:225,36,:226,72,:227,144,:228,61,:229,122,:230,244,:231,245,:232,247,:233,243,:234,251,:235,235,:236,203,:237,139,:238,11,:239,22,:240,44,:241,88,:242,176,:243,125,:244,250,:245,233,:246,207,:247,131,:248,27,:249,54,:250,108,:251,216,:252,173,:253,71,:254,142,:255,1,:"); - var gField2 = QRCodeGenerator.GaloisField._galoisFieldByIntegerValue; + var gField2 = QRCodeGenerator.GaloisField._alphaExponentByIntegerValue; gField2.Length.ShouldBe(256); var checkString2 = string.Empty; for (int i = 0; i < gField2.Length; i++) @@ -28,6 +28,78 @@ public void validate_antilogtable() checkString2.ShouldBe("0,0,:1,0,:2,1,:3,25,:4,2,:5,50,:6,26,:7,198,:8,3,:9,223,:10,51,:11,238,:12,27,:13,104,:14,199,:15,75,:16,4,:17,100,:18,224,:19,14,:20,52,:21,141,:22,239,:23,129,:24,28,:25,193,:26,105,:27,248,:28,200,:29,8,:30,76,:31,113,:32,5,:33,138,:34,101,:35,47,:36,225,:37,36,:38,15,:39,33,:40,53,:41,147,:42,142,:43,218,:44,240,:45,18,:46,130,:47,69,:48,29,:49,181,:50,194,:51,125,:52,106,:53,39,:54,249,:55,185,:56,201,:57,154,:58,9,:59,120,:60,77,:61,228,:62,114,:63,166,:64,6,:65,191,:66,139,:67,98,:68,102,:69,221,:70,48,:71,253,:72,226,:73,152,:74,37,:75,179,:76,16,:77,145,:78,34,:79,136,:80,54,:81,208,:82,148,:83,206,:84,143,:85,150,:86,219,:87,189,:88,241,:89,210,:90,19,:91,92,:92,131,:93,56,:94,70,:95,64,:96,30,:97,66,:98,182,:99,163,:100,195,:101,72,:102,126,:103,110,:104,107,:105,58,:106,40,:107,84,:108,250,:109,133,:110,186,:111,61,:112,202,:113,94,:114,155,:115,159,:116,10,:117,21,:118,121,:119,43,:120,78,:121,212,:122,229,:123,172,:124,115,:125,243,:126,167,:127,87,:128,7,:129,112,:130,192,:131,247,:132,140,:133,128,:134,99,:135,13,:136,103,:137,74,:138,222,:139,237,:140,49,:141,197,:142,254,:143,24,:144,227,:145,165,:146,153,:147,119,:148,38,:149,184,:150,180,:151,124,:152,17,:153,68,:154,146,:155,217,:156,35,:157,32,:158,137,:159,46,:160,55,:161,63,:162,209,:163,91,:164,149,:165,188,:166,207,:167,205,:168,144,:169,135,:170,151,:171,178,:172,220,:173,252,:174,190,:175,97,:176,242,:177,86,:178,211,:179,171,:180,20,:181,42,:182,93,:183,158,:184,132,:185,60,:186,57,:187,83,:188,71,:189,109,:190,65,:191,162,:192,31,:193,45,:194,67,:195,216,:196,183,:197,123,:198,164,:199,118,:200,196,:201,23,:202,73,:203,236,:204,127,:205,12,:206,111,:207,246,:208,108,:209,161,:210,59,:211,82,:212,41,:213,157,:214,85,:215,170,:216,251,:217,96,:218,134,:219,177,:220,187,:221,204,:222,62,:223,90,:224,203,:225,89,:226,95,:227,176,:228,156,:229,169,:230,160,:231,81,:232,11,:233,245,:234,22,:235,235,:236,122,:237,117,:238,44,:239,215,:240,79,:241,174,:242,213,:243,233,:244,230,:245,231,:246,173,:247,232,:248,116,:249,214,:250,244,:251,234,:252,168,:253,80,:254,88,:255,175,:"); } + [Fact] + public void validate_create_generator_polynom() + { + for (int numEccWords = 1; numEccWords < 32; numEccWords++) + { + var expected = GaloisFieldPolynomial.CalculateGeneratorPolynomial(numEccWords); + + var actual = QRCodeGenerator.Polynom.CreateGeneratorPolynom(numEccWords); + + actual.Count.ShouldBe(numEccWords + 1); + actual.Count.ShouldBe(expected.Degree + 1); + + for (int i = 0; i < actual.Count; i++) + { + actual[i].Exponent.ShouldBe(numEccWords - i); + actual[i].Coefficient.ShouldBe(expected[actual[i].Exponent]); + } + } + } + + private readonly struct GaloisFieldPolynomial + { + public GaloisFieldPolynomial(int degree) + { + Coefficients = new byte[degree + 1]; + } + + private byte[] Coefficients { get; } + + public int Degree => Coefficients.Length - 1; + + public ref byte this[int index] => ref Coefficients[index]; + + public static GaloisFieldPolynomial CalculateGeneratorPolynomial(int numEccWords) + { + var generatorPolynomial = new GaloisFieldPolynomial(1); + var multiplierPolynomial = new GaloisFieldPolynomial(1); + + for (byte i = 1; i < numEccWords; i++) + { + multiplierPolynomial[0] = i; + + generatorPolynomial *= multiplierPolynomial; + } + + return generatorPolynomial; + } + + public static GaloisFieldPolynomial operator *(GaloisFieldPolynomial left, GaloisFieldPolynomial right) + { + var result = new GaloisFieldPolynomial(left.Degree + right.Degree); + + for (int i = 0; i < left.Coefficients.Length; i++) + { + for (int j = 0; j < right.Coefficients.Length; j++) + { + result[i + j] ^= QRCodeGenerator.GaloisField._integerValueByAlphaExponent[ShrinkAlphaExp(left[i] + right[j])]; + } + } + + for (int i = 0; i < result.Coefficients.Length; i++) + { + result[i] = QRCodeGenerator.GaloisField._alphaExponentByIntegerValue[result[i]]; + } + + return result; + + static byte ShrinkAlphaExp(int alphaExp) + => (byte)(alphaExp + (alphaExp >>> 8)); + } + } + #if !NETFRAMEWORK // [Theory] is not supported in xunit < 2.0.0 [Theory] [InlineData("54321", ECCLevel.Default, "ZfnO93tpy9jjaACKXue2VsACXxY", 11)] //verified From b8968a14f7d2bb5ff2b03dbf23f60c3208634d7b Mon Sep 17 00:00:00 2001 From: Kris Vandermotten Date: Sun, 23 Aug 2026 14:10:00 +0200 Subject: [PATCH 2/2] Stronger assertions on numEccWords --- QRCoder/QRCodeGenerator/ECCInfo.cs | 4 ++-- QRCoder/QRCodeGenerator/Polynom.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/QRCoder/QRCodeGenerator/ECCInfo.cs b/QRCoder/QRCodeGenerator/ECCInfo.cs index 2af53b73..bab26510 100644 --- a/QRCoder/QRCodeGenerator/ECCInfo.cs +++ b/QRCoder/QRCodeGenerator/ECCInfo.cs @@ -23,7 +23,7 @@ private readonly struct ECCInfo public ECCInfo(int version, ECCLevel errorCorrectionLevel, int totalDataCodewords, int eccPerBlock, int blocksInGroup1, int codewordsInGroup1, int blocksInGroup2, int codewordsInGroup2) { - Debug.Assert(eccPerBlock < 32); + Debug.Assert(eccPerBlock is > 0 and < 32); Version = version; ErrorCorrectionLevel = errorCorrectionLevel; TotalDataCodewords = totalDataCodewords; @@ -45,7 +45,7 @@ public ECCInfo(int version, ECCLevel errorCorrectionLevel, int totalDataCodeword /// The number of error correction codewords per block. public ECCInfo(int version, ECCLevel errorCorrectionLevel, int totalDataCodewords, int totalDataBits, int eccPerBlock) { - Debug.Assert(eccPerBlock < 32); + Debug.Assert(eccPerBlock is > 0 and < 32); Version = version; ErrorCorrectionLevel = errorCorrectionLevel; TotalDataCodewords = totalDataCodewords; diff --git a/QRCoder/QRCodeGenerator/Polynom.cs b/QRCoder/QRCodeGenerator/Polynom.cs index 6713fe9c..cf46495d 100644 --- a/QRCoder/QRCodeGenerator/Polynom.cs +++ b/QRCoder/QRCodeGenerator/Polynom.cs @@ -55,7 +55,7 @@ internal struct Polynom : IDisposable /// A polynomial that can be used to generate ECC codewords. public static Polynom CreateGeneratorPolynom(int numEccWords) { - Debug.Assert(numEccWords < 32); + Debug.Assert(numEccWords is > 0 and < 32); int startIndex = (numEccWords - 1) * numEccWords / 2;