如何验证UPC或EAN码?

29

2
@Zack 我假设你现在已经有了答案,但我想指出,如果你的系统打算处理ISBN-10代码(随着旧书下架,这些代码最终会消失),你需要包括一个检查。虽然你的问题是关于GTIN的,但ISBN-10可以转换为ISBN-13,这相当于EAN / GTIN-13。为什么:ISBN-10是模11,因此使用字母“X”作为可能的校验位来表示数字10。只寻找数字会失败,除非你先转换为ISBN-13。 - Zack Jannsen
仅供日后参考,该算法的描述在此处。 - jeroenh
17个回答

30
public static bool IsValidGtin(string code)
{
    if (code != (new Regex("[^0-9]")).Replace(code, ""))
    {
        // is not numeric
        return false;
    }
    // pad with zeros to lengthen to 14 digits
    switch (code.Length)
    {
        case 8:
            code = "000000" + code;
            break;
        case 12:
            code = "00" + code;
            break;
        case 13:
            code = "0" + code;
            break;
        case 14:
            break;
        default:
            // wrong number of digits
            return false;
    }
    // calculate check digit
    int[] a = new int[13];
    a[0] = int.Parse(code[0].ToString()) * 3;
    a[1] = int.Parse(code[1].ToString());
    a[2] = int.Parse(code[2].ToString()) * 3;
    a[3] = int.Parse(code[3].ToString());
    a[4] = int.Parse(code[4].ToString()) * 3;
    a[5] = int.Parse(code[5].ToString());
    a[6] = int.Parse(code[6].ToString()) * 3;
    a[7] = int.Parse(code[7].ToString());
    a[8] = int.Parse(code[8].ToString()) * 3;
    a[9] = int.Parse(code[9].ToString());
    a[10] = int.Parse(code[10].ToString()) * 3;
    a[11] = int.Parse(code[11].ToString());
    a[12] = int.Parse(code[12].ToString()) * 3;
    int sum = a[0] + a[1] + a[2] + a[3] + a[4] + a[5] + a[6] + a[7] + a[8] + a[9] + a[10] + a[11] + a[12];
    int check = (10 - (sum % 10)) % 10;
    // evaluate check digit
    int last = int.Parse(code[13].ToString());
    return check == last;
}

对了,UPC 码中的最后一位是「模数校验」数字。关于这个,Petzold 的书《Code》中有一个很容易理解的解释。 - ybakos
我希望你不打算在这个系统上扫描一个ISBN-10条形码(在ISBN-13之前,书籍使用了它)。ISBN-10是模11的。我可以将字母“X”作为校验位来表示数字10。可能的解决方案:先转换为ISBN-13(相当于EAN-13 / GTIN-13)。 - Zack Jannsen
注意:ISBN-10于2007年1月1日被ISBN-13替代。这并不意味着当前货架上的书籍不会同时使用两种代码(向后兼容)。如果该系统具有人类输入界面,并且有任何可能涉及到图书,您将希望防止他们选择ISBN-10代码。 - Zack Jannsen
1
ISBN-10 转 EAN-13 可以通过在数字前面添加 978 来实现。注意:978 和 979 分别用于 ISBN 范围,只有当 978 用完后才会使用 979。然而,979 不会有一个 10 位数字的对应项,因此您可以安全地构建代码,通过添加 978 来获取 10 位 ISBN。只需注意,您需要根据 GTIN-13 模数 10 规则计算新号码的校验位。 - Zack Jannsen
3
有点笨重的方法。在我看来,使用单个循环会更好。 - ThunderGr
谢谢。我用以下代码替换了SWITCH语句:if ("|8|12|13|14|".Contains("|" + Convert.ToString(code.Length) + "|")) { code = p_code.PadLeft(14, Convert.ToChar("0")); } else { return false; } - user1932634

25

GS1 USеҸ‘еёғдәҶдёҖдёӘPDFж–ҮжЎЈпјҢе…¶дёӯеҢ…еҗ«и®Ўз®—GTINж ЎйӘҢдҪҚзҡ„з®—жі•пјҲе·ІеҲ йҷӨдёҚж–ӯеҸҳеҢ–зҡ„й“ҫжҺҘпјүгҖӮ

д»ҘдёӢд»Јз ҒдҪҝз”ЁlinqжЈҖжҹҘGTINжқЎеҪўз Ғзҡ„жңҖеҗҺдёҖдҪҚпјҡGTIN-8пјҢGTIN-12пјҲUPCпјүпјҢGTIN-13пјҲEANпјүе’ҢGTIN-14пјҲITF-14пјүгҖӮ

private static Regex _gtinRegex = new System.Text.RegularExpressions.Regex("^(\\d{8}|\\d{12,14})$");
public static bool IsValidGtin(string code)
{
    if (!(_gtinRegex.IsMatch(code))) return false; // check if all digits and with 8, 12, 13 or 14 digits
    code = code.PadLeft(14, '0'); // stuff zeros at start to garantee 14 digits
    int[] mult = Enumerable.Range(0, 13).Select(i => ((int)(code[i] - '0')) * ((i % 2 == 0) ? 3 : 1)).ToArray(); // STEP 1: without check digit, "Multiply value of each position" by 3 or 1
    int sum = mult.Sum(); // STEP 2: "Add results together to create sum"
    return (10 - (sum % 10)) % 10 == int.Parse(code[13].ToString()); // STEP 3 Equivalent to "Subtract the sum from the nearest equal or higher multiple of ten = CHECK DIGIT"
}

我已经从我的生产代码中删除了将字符解析为整数的部分。参考:https://dev59.com/kHA65IYBdhLWcg3wuhIR - Luciano Carvalho
非常简洁。但是,通过将((int)(code[i] - '0'))替换为((int)char.GetNumericValue(code[i])),您可以使它稍微更易读。 - NightOwl888
1
非常棒。在生产代码中使用它,似乎运行得非常完美! - Martin Hertig

25

以上解决方案计算校验位并将其与给定的数字进行比较,忽略了它被设计为以更简单的方式进行验证的事实。

  1. 将所有数字(包括校验位)乘以3或1并求和。
  2. 检查总和是否是10的倍数

根据Luciano的回答:

private static Regex _gtinRegex = new Regex("^(\\d{8}|\\d{12,14})$");
public static bool IsValidGtin(string code)
{
    if (!(_gtinRegex.IsMatch(code))) return false;
    code = code.PadLeft(14, '0');
    int sum = code.Select((c,i) => (c - '0')  * ((i % 2 == 0) ? 3 : 1)).Sum();
    return (sum % 10) == 0;
}

12

可变长度的EAN码

    public static bool IsValidEan13(string eanBarcode)
    {
        return IsValidEan(eanBarcode, 13);
    }

    public static bool IsValidEan12(string eanBarcode)
    {
        return IsValidEan(eanBarcode, 12);
    }

    public static bool IsValidEan14(string eanBarcode)
    {
        return IsValidEan(eanBarcode, 14);
    }

    public static bool IsValidEan8(string eanBarcode)
    {
        return IsValidEan(eanBarcode, 8);
    }

    private static bool IsValidEan(string eanBarcode, int length)
    {
        if (eanBarcode.Length != length) return false;
        var allDigits = eanBarcode.Select(c => int.Parse(c.ToString(CultureInfo.InvariantCulture))).ToArray();
        var s = length%2 == 0 ? 3 : 1;
        var s2 = s == 3 ? 1 : 3;
        return allDigits.Last() == (10 - (allDigits.Take(length-1).Select((c, ci) => c*(ci%2 == 0 ? s : s2)).Sum()%10))%10;
    }

    [Test]
    [TestCaseSource("Ean_13_TestCases")]
    public void Check_Ean13_Is_Valid(string ean, bool isValid)
    {
        BlinkBuilder.IsValidEan13(ean).Should().Be(isValid);
    }

    private static IEnumerable<object[]> Ean_13_TestCases()
    {
        yield return new object[] { "9781118143308", true };
        yield return new object[] { "978111814330", false };
        yield return new object[] { "97811181433081", false };
        yield return new object[] { "5017188883399", true };
    }

    [Test]
    [TestCaseSource("Ean_8_TestCases")]
    public void Check_Ean8_Is_Valid(string ean, bool isValid)
    {
        BlinkBuilder.IsValidEan8(ean).Should().Be(isValid);
    }

    private static IEnumerable<object[]> Ean_8_TestCases()
    {
        yield return new object[] { "12345670", true };
        yield return new object[] { "12345679", false };
        yield return new object[] { "55432214", true  };
        yield return new object[] { "55432213", false };
        yield return new object[] { "55432215", false };
    }

编辑

我为这段代码构建的项目现在已经上线并运行正常 - 它是一个综合性的条形码数据库和工具集的一部分 - 包括批量条形码验证器(非注册用户可以验证100个,注册用户可以验证10,000个)- https://blinked.in/tools/validator


1
这个答案中的链接无效,测试用例的代码使用了它。 - Chris Nevill

3
我也需要验证大量的EAN-13码,当我搜索时发现了这个问题。我不喜欢最受欢迎的答案的外观。在处理大型数据集时会有太多可能的字符串分配。与仅验证每个字符是否为数字相比,正则表达式也被证明是缓慢的。
在我的8851个EAN-13号码数据集上运行最受欢迎的问题中使用的验证逻辑花费了47毫秒,在我的旧笔记本电脑上,而我的实现仅花费2毫秒。这里也有更少的字符串分配。
private static bool IsValidGtin(ReadOnlySpan<char> input, byte length)
{
    if (input.Length != length)
    {
        return false;
    }

    if (!char.IsDigit(input[^1]))
    {
        return false;
    }

    var sum = 0d;
    var multiplyByThree = true;
    var inputWithoutCheckDigit = input[..^1];
    for (var i = inputWithoutCheckDigit.Length - 1; i >= 0; i--)
    {
        var currentChar = inputWithoutCheckDigit[i];
        if (!char.IsDigit(currentChar))
        {
            return false;
        }

        var value = char.GetNumericValue(currentChar);
        if (multiplyByThree)
        {
            sum += value * 3;
        }
        else
        {
            sum += value;
        }

        multiplyByThree = !multiplyByThree;
    }
    
    var checkDigit = char.GetNumericValue(input[^1]);

    return (sum + checkDigit) % 10 == 0;

}

我写了更多关于它的细节在这里,如果需要。

这是一种精准的数学方法。 - undefined

2

我喜欢它,代码参考了这个问题:D - Anton Hinkel

1
/// <summary>
/// Validates a GTIN (UPC/EAN) using the terminating check digit
/// </summary>
/// <param name="code">the string representing the GTIN</param>
/// <returns>True if the check digit matches, false if the code is not 
/// parsable as a GTIN or the check digit does not match</returns>
public static bool IsValidGtin(string code)
{
    if (string.IsNullOrWhiteSpace(code))
        return false;
    if (code.Length != 8 && code.Length != 12 && code.Length != 13 
        && code.Length != 14)
        // wrong number of digits
        return false;

    int sum = 0;
    for (int i = 0; i < code.Length - 1 /* do not include check char */; i++)
    {
        if (!char.IsNumber(code[i]))
            return false;

        var cchari = (int)char.GetNumericValue(code[i]);
        // even (from the right) characters get multiplied by 3
        // add the length to align right
        if ((code.Length + i) % 2 == 0)
            sum += cchari * 3;
        else
            sum += cchari;
    }

    // validate check char
    char checkChar = code[code.Length - 1];
    if (!char.IsNumber(checkChar))
        return false;

    int checkChari = (int)char.GetNumericValue(checkChar);
    return checkChari == (10 - (sum % 10)) % 10;
}

测试用例:

    [TestMethod()]
    public void IsValidGtinTest_Valid()
    {
        string[] valid = new[] {
            "085126880552",
            "0085126880552",
            "00085126880552",
            "0786936226355",
            "0719852136552"
        };
        foreach (var upc in valid)
            Assert.IsTrue(IdentifierUtilities.IsValidGtin(upc), upc);
    }

    [TestMethod()]
    public void IsValidGtinTest_Invalid()
    {
        string[] invalid = new[] {
            "0058126880552",
            "58126880552",
            "0786936223655",
            "0719853136552",
            "",
            "00",
            null,
            "123456789123456789123456789",
            "1111111111111"
        };
        foreach (var upc in invalid)
            Assert.IsFalse(IdentifierUtilities.IsValidGtin(upc), upc);
    }

1
    private bool ValidateCheckDigit()
    {

        Int32 _num = 0;
        Int32 _checkdigit = 0;

        for (int i = 0; i < CurrentUpcInfo.UpcCode.Length; i++)
        {
            if (i % 2 == 0)
            {
                _num += (3 * Convert.ToInt32(CurrentUpcInfo.UpcCode.Substring(i, 1)));
            }
            else
            {
                _num += Convert.ToInt32(CurrentUpcInfo.UpcCode.Substring(i, 1));
            }

        }
        _num = Math.Abs(_num) + 10;  // in case num is a zero
        _checkdigit = (10 - (_num % 10)) % 10;


        if (Convert.ToInt32(CurrentUpcInfo.Checkdigit) == _checkdigit)
            return true;

        return false;

    }

1
我曾遇到相似的问题,谷歌搜索后找到了这个页面。我需要为标签生成程序计算大量条形码的校验位。起初,我尝试了Luciano Carvalho上面的答案变化版,但我对将字符串转换为字符再转换为整数有些好奇。我猜想我可能能够提高一点性能。

请注意,验证是在此函数之外进行的。该函数更多地是为了速度而构建,因为我正在生成大量的条形码。

int CalculateCheckDigit(ulong label)
{
    int sum = 0;
    bool isEven=true;
    while(label>0)
    {
        if(isEven)
            sum += (int)(label % 10) * 3;
        else
            sum += (int)(label % 10) * 1;
        isEven = !isEven;
        label /= 10;
    }

    return (10 - (sum % 10)) % 10;
}

我在我的代码中实际使用的解决方案是避免解析的char c,(c - '0'),就像https://dev59.com/kHA65IYBdhLWcg3wuhIR中所示。 - Luciano Carvalho

0

def check_digit():

        users_gtin=raw_input("enter first seven digits of gtin ")

        gtin_seven_digits=unicode(users_gtin)
        
        
        if len(gtin_seven_digits) == 7 and gtin_seven_digits.isnumeric():
            ck = ((((int(gtin_seven_digits[0])) + (int(gtin_seven_digits[2])) + (int(gtin_seven_digits[4])) + (int(gtin_seven_digits[6])))*3) + ((int(gtin_seven_digits[1])) + (int(gtin_seven_digits[3])) + (int(gtin_seven_digits[5])))) %10
            final_ck = 10-ck

            if final_ck == 10:
                final_ck=0
                print "Is your check digit",final_ck,"?"
        
            else:
                print "Is your check digit",final_ck,"?"
                
        else:
            print "please try typing an seven digit number"
            check_digit()
                                                                                                                                    

        choice=raw_input("enter (a) to restart or press anything other than the letter  a  to end this program <<<< ").upper()
        

        if choice == "A":
            check_digit()
        

check_digit()

可能不是最高效的,但希望它能有所帮助..


如果您解释一下您的答案,而不是仅仅编写代码,这可能会有所帮助,因为他们可以从中学到更多。 - winhowes
抱歉,我是新来的,不太清楚如何添加评论。我发现使用 # 会使代码看起来更复杂、更难理解,但不知道如何添加描述。@winhowes - Ben Russell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接