2

有许多条码类型和大小(长度)。是否有一组通用算法可用于计算任何条形码的校验和?

4

2 回答 2

4

是的,有一个非常常见的校验和计算器算法。各种条形码(和其他数字输入方案)使用它们来验证扫描仪(或人类)正确输入了所有数字。第一个例子,也是最普遍理解的校验和算法是 Luhn 算法,它以在信用卡上的使用而闻名。但它存在许多变化。然而,在它们的核心,大多数使用相同的算法。

通用算法使用与数字位置相对应的权重数组、模除数和指示“乘积添加”或“乘积数字添加”方案的标志。

伪代码

int:computeChecksum(string:inputData, int[len(inputData)]: weightArray, int:divisor, Boolean:productDigitAdd)
{

    If the number of digits in inputData is not equal to the number of elements in weightArray
        raise an invalidWeightArray exception
    endif

    create an int:checksum and set it to zero

    For int:position = each digit in inputData
        int:digitProduct = value of inputData digit at [position] times the weightArray at [position] 
        If productDigitAdd then
            for int:prodPosition = each digit in digitProduct
                checksum = checksum + digitProduct[prodPosition]
            end for
        else
            checksum = checksum + digitProduct
        end if
    end for

    int:remainder = checksum modulo divided by divisor
    return remainder
}

常见方案

校验位方案在各个行业中普遍使用,条码制作和验证只是其中之一。以下是可用于此算法的一些常见校验位方案的列表:

  • UPC-A条码:权重为{3,1,3,1,3,1,3,1,3,1,3,1}的十二位数字,除数为10,productDigitAdd值为false。
  • EAN-13 条码:权重为 {1,3,1,3,1,3,1,3,1,3,1,3,1},除数为 10,productDigitAdd 值为 false。
  • Code 128 条码:数字是条码的值,而不是条码包含的值(例如条码符号值 65 代表模式 A 和 B 中的 ASCII 字母 'A',而是一对数字' 33' 模式 C); 权重是{1, 2, 3, 4, 5 ...通过条码符号的总数,1},其中1是右端的校验位权重;除数是103;并且 productDigitAdd 值为 false。
  • POSTNET条码:权重为{1,1,1,1,1,1,1,1,1,1},除数为10,productDigitAdd值为false。
  • 16 位信用卡:权重为十六位数组,{2,1,2,1 ... 2,1},除数为 10,productDigitAdd 值为 true。这就是卢恩算法。
  • ISBN(书号):权重为{10,9,8,7,6,5,4,3,2,1},除数为11,productDigitAdd字段为false。
  • Routing Transit Number(在银行支票上):权重为 {3,7,1,3,7,1,3,7,1},除数为 10,productDigitAdd 字段为 false。

验证

要使用此算法验证条形码,只需将其输出与零进行比较。非零值表示失败。

一个常见的错误是尝试隔离校验位,然后将例程的输出与被隔离的校验位进行比较。简单地在整个循环中包含校验位,然后将输出与零进行比较,会更简单、更安全。然后,该算法继续适用于嵌入在条形码右端以外的位置的校验位,或者校验位具有非一个权重的位置。

一代

您还可以使用与生成新条形码时相同的算法来计算校验位。在您的输入数据中,其中一位数字将保留为校验位的位置。这通常是最右边的数字,但并非总是如此。将输入数字数组的位置设置为零。像验证 inputData 一样调用算法,然后从模除数中减去算法的输出。用减去的输出替换输入数字数组中的零占位符。

特别案例

Luhn 算法的实现通常非常非正式。一种常见的描述方式是“每隔一个数字加倍,然后将和的数字相加,最后一个数字必须是校验位”。这简单地适用于 16 位数字卡,但可能导致不灵活的代码,通常使用 case 语句来处理不同长度的卡号等。

ISBN 编号(和其他编号)使用 11 的除数,但这会产生“10”作为输出校验位。两位数的值不适合为一位数保留的条形码位置。ISBN 规范说“10 应替换为字母‘X’”。我遇到的其他方案只是简单地丢弃任何产生两位校验位结果的数字。

对于像这样或其他使用非数字值的方案,例如在美国和加拿大销售的汽车上的车辆识别码 (VIN),一种可靠的方法是将此例程从接受“字符串”作为输入数据,改为将其转换为接受输入值数组。然后在此例程之前和之后对数组执行转换步骤,以将给定符号映射到必要的值。通常我会保留这个例程的一个版本,专门用于接受字符串类型的输入,因为大多数程序将条形码作为字符串处理。

我遇到过在权重数组中包含负值的旧校验位方案。它们从总和中减去而不是相加,但除此之外一切都以相同的方式工作。

一个非常常见的优化,特别是在条形码扫描仪等嵌入式设备中,是接受一个比位数短的权重数组,并将其向左扩展与数字中的位数一样多的位置。这样一来,所有 UPC 和 EAN 方案,包括 UPC-A、UPC-E、EAN-8 和 EAN-13,都与一个通用例程相匹配。然后权重为 {3,1},除数为 10,PDA 为假。扩展权重必须通过将最右边的权重数字锚定到最右边的 inputData 数字来完成,因此 31 的权重变为

         <-31
1313131313131
9780321146533

相同的技术可以处理任何重复的权重集。对于使用 21 权重的信用卡的 Luhn 算法,相同的例程适用于 13 位数的 Visa 卡、15 位数的 AmEx 卡和 16 位数的 Visa 卡。但它随后需要对位数长度进行外部验证,因此并没有节省多少。

于 2013-09-10T17:51:12.113 回答
1

条码虽然长度不同,语义不同,但在生成校验和时仍然遵循一些基本规则。下面的代码可以复制到一个小工具中进行测试(或者简单地使用非事件处理代码):

要使用它,请创建一个 WinForms 应用程序,并在其上放置以下控件:

0) A button named button1; give it text something like "Calculate barcode and append it to label below", if desired
1) A button named button2; give it text something like "Valid barcode + czech digit", if desired
2) A label named label1, which displays the result of clicking button1
3) A textBox named textBox1, wherein you enter a raw barcode value (sans check digit) prior to clicking button1 OR enter a full barcode value (with check digit) prior to clicking button2

// "Calculate check sum" handler
private void button1_Click(object sender, EventArgs e)
{
    string barcodeWithoutCheckSum = textBox1.Text.Trim();
    string checkSum = GetBarcodeChecksum(barcodeWithoutCheckSum);
    string barcodeWithCheckSum = string.Format("{0}{1}", barcodeWithoutCheckSum, checkSum);
    label1.Text = barcodeWithCheckSum;
    textBox1.Focus();
}

// Verify/validate existing checksum handler
private void button2_Click(object sender, EventArgs e)
{
    string bcVal = textBox1.Text.Trim();
    bool validCheckDigit = isValidBarcodeWithCheckDigit(bcVal);
    MessageBox.Show(validCheckDigit ? string.Format("{0} is valid", bcVal) : string.Format("{0} invalid", bcVal));
}

public static string GetBarcodeChecksum(string barcode)
{
    int oddTotal;
    int oddTotalTripled;
    int evenTotal;
    // Which positions are odd or even depend on the length of the barcode, 
    // or more specifically, whether its length is odd or even, so:
    if (isStringOfEvenLen(barcode))
    {
        oddTotal = sumInsideOrdinals(barcode);
        oddTotalTripled = oddTotal * 3;
        evenTotal = sumOutsideOrdinals(barcode);
    }
    else
    {
        oddTotal = sumOutsideOrdinals(barcode);
        oddTotalTripled = oddTotal * 3;
        evenTotal = sumInsideOrdinals(barcode);
    }
    int finalTotal = oddTotalTripled + evenTotal;
    int modVal = finalTotal%10;
    int checkSum = 10 - modVal;
    if (checkSum == 10)
    {
        return "0";
    }
    return checkSum.ToString();
}

private static bool isStringOfEvenLen(string barcode)
{
    return (barcode.Length % 2 == 0);
}

// "EvenOrdinals" instead of "EvenVals" because values at index 0,2,4,etc. are seen by the 
// checkdigitmeisters as First, Third, Fifth, ... (etc.), not Zeroeth, Second, Fourth
private static int sumInsideOrdinals(string barcode)
{
    int cumulativeVal = 0;
    for (int i = barcode.Length-1; i > -1; i--)
    {
        if (i % 2 != 0)
        {
            cumulativeVal += Convert.ToInt16(barcode[i] - '0');
        }
    }
    return cumulativeVal;
}

// "OddOrdinals" instead of "OddVals" because values at index 1,3,5,etc. are seen by the 
// checkdigitmeisters as Second, Fourth, Sixth, ..., not First, Third, Fifth, ...
private static int sumOutsideOrdinals(string barcode)
{
    int cumulativeVal = 0;
    for (int i = barcode.Length - 1; i > -1; i--)
    {
        if (i % 2 == 0)
        {
            cumulativeVal += Convert.ToInt16(barcode[i] - '0');
        }
    }
    return cumulativeVal;
}

private static bool isValidBarcodeWithCheckDigit(string barcodeWithCheckDigit)
{
    string barcodeSansCheckDigit = barcodeWithCheckDigit.Substring(0, barcodeWithCheckDigit.Length - 1);
    string checkDigit = barcodeWithCheckDigit.Substring(barcodeWithCheckDigit.Length - 1, 1);
    //MessageBox.Show(string.Format("raw barcode portion is {0}", barcodeSansCheckDigit));
    //MessageBox.Show(string.Format("check portion is {0}", checkDigit));
    return GetBarcodeChecksum(barcodeSansCheckDigit) == checkDigit;
}
于 2013-09-04T23:11:54.193 回答