【问题标题】:What code do I need to calculate a checksum for any barcode, and to verify/validate existing checksums?我需要什么代码来计算任何条形码的校验和,并验证/验证现有的校验和?
【发布时间】:2013-09-04 23:09:53
【问题描述】:

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

【问题讨论】:

    标签: c# barcode barcode-scanner checksum


    【解决方案1】:

    是的,有一个非常常见的校验和计算器算法。各种条形码(和其他数字输入方案)使用它们来验证扫描仪(或人类)正确输入了所有数字。第一个例子,也是最普遍理解的校验和算法是 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 .
    • Code 128 条码:数字是条码的值,而不是条码包含的值(例如,条码符号值 65 表示模式 A 和 B 中的 ASCII 字母 'A',但是是对模式 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。
    • 16 位信用卡:权重为十六位数组,{2,1,2,1 ... 2,1},除数为 10,productDigitAdd 值为 true。这是 Luhn 算法。
    • 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’”。我遇到的其他方案只是将任何产生两位校验位结果的数字视为不可能。

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

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

    一个非常常见的优化,尤其是在条形码扫描仪等嵌入式设备中,是接受一个比位数短的权重数组,并将其向左扩展与数字中的位数一样多的位置。这样一来,所有 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 卡。但是它需要外部验证位数的长度,所以它并没有节省多少。

    【讨论】:

    • 我唯一关心的三个(目前,无论如何)是前三个(UPCA、EAN13 和 128)。但是,UPCA 和 EAN13 中的最后/尾随“1”不是校验位吗?由于这个原因,我的计算显示这两个乘数最后都是“3”。 IOW, EAN13 是 1,3,1,3,1,3,1,3,1,3,1,3, checkDigit, IOW 12 个条形码 vals + 一个校验位 val,总共 13 个。与 UPCA 类似。我已经使用这种方法成功检查了 UPCA 和 EAN13 的结果。 128 是一个奇怪的 - 感谢您提供的信息,因为如果没有它,我的 128 算法将是错误的。
    • UPC-A 是 12 位,所以当你从 3 开始时你会以 1 结束。EAN-13 是 13 位,所以当你从 1 开始时,你会以 1 结束。但尽量不要想将其视为“每隔一个数字”,将其视为一组权重。
    • 哦,我想我明白你在说什么。该算法不关心哪个数字被指定为校验位。只要所有数字的总和(除以模数)为零,它就通过了。但这意味着您需要在权重和长度中包含校验位,就像其他任何数字一样。
    • @ClayShannon,我修复了上面的 Code128 校验位解释(对不起,我错了。)只是发布这个希望你能在使用旧代码之前看到它。
    【解决方案2】:

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

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

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-20
    • 2020-08-11
    相关资源
    最近更新 更多