我需要用什么代码来计算任何条形码的校验和,并验证/验证现有的校验和?

时间:2013-09-04 23:09:53

标签: c# barcode barcode-scanner checksum

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

2 个答案:

答案 0 :(得分:3)

是的,有一种非常常见的校验和计算器算法。各种条形码(和其他数字输入方案)使用它们来验证扫描仪(或人)正确输入所有数字。第一个例子之一,最常用的校验和算法是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值是假的。
  • EAN-13条形码:权重为{1,3,1,3,1,3,1,3,1,3,1,3,1},除数为10,productDigitAdd值为false
  • 代码128条形码:数字是条形码的值,而不是条形码包含的值(例如,条形码符号值65表示ASCII字母' A'在模式A和B中,但是在模式C)中是数字对' 33;权重是{1,2,3,4,5 ......通过条形码符号的总数,1},其中1是右端的校验位权重;除数是103;并且productDigitAdd值为false。
  • POSTNET条形码:权重为{1,1,1,1,1,1,1,1,1,1},除数为10,productDigitAdd值为false。
  • 信用卡:权重是十六位数组,{2,1,2,1 ... 2,1},除数为10,productDigitAdd值为true。这是Luhn算法。
  • ISBN(书号):权重为{10,9,8,7,6,5,4,3,2,1},除数为11,productDigitAdd字段为false。
  • 路由公交号码(在银行支票上):权重为{3,7,1,3,7,1,3,7,1},除数为10,productDigitAdd字段为false。

验证

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

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

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

特殊情况

请注意,对于ISBN号,除数为11的情况下,这可以产生" 10"作为输出数字。但是两位数值不适合保留给一位数的条形码位置。 ISBN规范说10应该用字母X代替。我遇到的其他方案只是丢弃,因为任何产生两位数校验位数字的数字都是不可能的。

对于此类或其他使用非数字值的方案,最好的方法是将此例程更改为接受"字符串"作为输入数据,而是将其转换为接受输入值数组。然后在此例程之前和之后执行转换步骤,以将给定符号映射到必要值。通常我会将此例程的一个版本专门用于接受输入的字符串类型,因为大多数程序将条形码作为字符串处理。

一种非常常见的优化,特别是在条形码扫描仪等嵌入式设备中,是接受一个比数字位数短的权重数组,并将数据的位数与数字中的数字一样多。这样,所有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卡。但它需要外部验证数字的长度,因此它不会节省太多。

答案 1 :(得分:0)

条形码尽管长度不同且具有不同的语义,但仍然遵循生成校验和的一些基本规则。下面的代码可以复制到一个小的util中进行测试(或者只是使用非事件处理代码):

要使用它,请创建一个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;
}