29

I need a C# .NET function to evaluate whether a typed or scanned barcode is a valid Global Trade Item Number (UPC or EAN).

barcode check digit

The last digit of a bar code number is a computer Check Digit which makes sure the bar code is correctly composed. GTIN Check Digit Calculator

Zack Peterson
  • 56,055
  • 78
  • 209
  • 280
  • 2
    @Zack I assume you have an answer by now, but I would like to point out that if your system intends to deal with the ISBN-10 codes (which will eventually go away as older books fall off the market) you need to include a check for this. Your question is specific to GTIN, but ISBN-10 can be converted to ISBN-13 which is equivalent to EAN / GTIN-13. Why: ISBN-10 is modulo 11 and as such uses the letter 'X' as a possible check digit to represent the number 10. Only looking for numbers would fail here, unless you convert to ISBN-13 first. – Zack Jannsen Apr 25 '13 at 11:15
  • just for future reference, the algorithm is described [here](https://www.gs1.org/services/how-calculate-check-digit-manually) – jeroenh Aug 16 '18 at 20:03

17 Answers17

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;
}
Zack Peterson
  • 56,055
  • 78
  • 209
  • 280
  • Right, the last digit in UPC codes is a "modulo check" digit. A really approachable explanation of this can be found in Petzold's book _Code_. – ybakos Apr 13 '12 at 15:22
  • I hope you don't intend to scan an ISBN-10 barcode on this system (books used this in the past before ISBN-13). ISBN-10 is modulo 11. I can have a letter 'X' as a check digit to represent the number 10. Possible Solution: either convert to ISBN-13 first (equivalent to EAN-13 / GTIN-13). – Zack Jannsen Apr 25 '13 at 11:23
  • NOTE: ISBN-10 was replaced January 1, 2007 by ISBN-13. That does not mean that the books on currently on the shelf will not carry both codes (backward compatibility). If this system has a human entry interface and has any chance of dealing with books you will want to safeguard against them picking the ISBN-10 code. – Zack Jannsen Apr 25 '13 at 11:35
  • 1
    The conversion from ISBN-10 to EAN-13 is possible by adding 978 to the front of the number. NOTE: 978 and 979 are designated for ISBN ranges and 979 will not be used until 978 is used up. 979, however, will not carry a 10 digit counterpart so you are safe to build code that takes a 10 digit ISBN by adding 978. Just note you will need to calculate the check digit for the new number based on GTIN-13 modulus 10 rules. – Zack Jannsen Apr 25 '13 at 13:15
  • 3
    A bit cumbersome approach. Using a single loop would work better, in my opinion – ThunderGr Sep 26 '13 at 06:48
  • Thanks. I replaced the SWITCH stmnt with: if ("|8|12|13|14|".Contains("|" + Convert.ToString(code.Length) + "|")) { code = p_code.PadLeft(14, Convert.ToChar("0")); } else { return false; } – user1932634 May 06 '16 at 13:21
25

GS1 US publishes the check digit calculation algorithm for GTIN in a PDF document (removed the constantly changing link).

The following code uses linq to check the last digit for GTIN barcodes: GTIN-8, GTIN-12 (UPC), GTIN-13 (EAN) and 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"
}
Luciano Carvalho
  • 1,739
  • 15
  • 15
  • Removed the parsing to int, that I actually didn't use on my production code. Ref.: http://stackoverflow.com/questions/3665757/c-sharp-convert-char-to-int – Luciano Carvalho Sep 07 '14 at 21:07
  • Very concise. However, you could make it slightly more readable by replacing `((int)(code[i] - '0'))` with `((int)char.GetNumericValue(code[i]))`. – NightOwl888 Mar 16 '16 at 18:36
  • 1
    Super nice. Using this in productive code and seems to be working flawless! – Martin Hertig Feb 21 '23 at 15:51
25

The solutions above calculate the check digit and compare it to the given digit, ignoring the fact that it is designed to be validated in a much simpler way.

  1. Multiply all digits, including check digit, by 3 or 1 and sum.
  2. Check if the sum is a multiple of 10

Based on Luciano's answer:

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;
}
AlexDev
  • 4,049
  • 31
  • 36
12

Variable length EANs

    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 };
    }

EDIT

The project I was building this code for is now up and running - it's part of a comprehensive barcode database and toolset - and includes a bulk barcode validator (100 in a batch for non-reg users, 10,000 for registered) - https://blinked.in/tools/validator

null loop
  • 196
  • 2
  • 5
2

I Found this Nuget Package: https://www.nuget.org/packages/ProductCodeValidator/

The code in github is: https://github.com/ThomasPe/ProductCodeValidator.

Here's how to use it:

using ProductCodeValidator;

bool IsValidEAN(string EAN)
{
    return EanValidator.IsValidEan(testEan));
}
jeroenh
  • 26,362
  • 10
  • 73
  • 104
Otávio Larrosa
  • 303
  • 2
  • 12
  • I love it, the [code](https://github.com/ThomasPe/ProductCodeValidator/blob/61e5674a905be7bda6cb657211f0ef3514608a2f/ProductCodeValidator/ValidatorHelpers.cs#L23) references this question :D – Anton Hinkel Jan 13 '23 at 15:12
2

I also needed to validate a large number of EAN-13 and found this question when i searched for it. I didn't like the look of the most upvoted answer. Too many possible string allocations when working with a large dataset. The regex also turned out to be slow, compared to just validating if each char is a digit or not.

Running the validation logic used in the most upvoted question on my dataset of 8851 EAN-13 numbers took 47 ms on my old laptop, while my own implementation only took 2 ms. There are also far fewer string allocations here.

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;

}

I wrote more details about it here if needed

Morten Toudahl
  • 452
  • 1
  • 4
  • 13
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;
}

Test cases:

    [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);
    }
Mitch
  • 21,223
  • 6
  • 63
  • 86
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;

    }
Dan Dwire
  • 11
  • 1
1

I had a similar problem and google brought me to this page. I needed to calculate the check digit for a large number of barcodes for a label generating program. I first started with a variation of Luciano Carvalho's answer above, but I was a little curious of the casting of the string to a char to an int. I suspected I might be able improve the performance a bit.

Please note that validating is happening outside of this function. This function is built more for speed since I am generating a large number of barcodes.

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;
}
BlueFuzzyThing
  • 101
  • 1
  • 4
  • The solution I actually use on my code to avoid parsing is for char c, (c - '0'), like in http://stackoverflow.com/questions/3665757/c-sharp-convert-char-to-int – Luciano Carvalho Sep 07 '14 at 21:04
0

I'm aware that the question is in the context of .net/C#. Nevertheless I landed on this page seeking answer to the same question, but in a Groovy context.

Since I actually succeeded in using the information on this page to come up with an answer to my own problem, I thought I'd share the result.
Especially the answers from AlexDev, Zack Peterson and Mitch was helpful to me.

/*
Check digit calculation is based on modulus 10 with digits in an odd
position (from right to left) being weighted 1 and even position digits
being weighted 3. 
For further information on EAN-13 see:
Wikipedia - European Article Number: http://en.wikipedia.org/wiki/International_Article_Number_%28EAN%29
Implementation based on http://stackoverflow.com/questions/10143547/how-do-i-validate-a-upc-or-ean-code
Tests can be found there too
*/
boolean isValidEan(String code){
  returnValue = false
  if (code ==~ /\d{8}|\d{12,14}/){ //Matches if String code contains 8, 12, 13 or 14 digits
    assert [8,12,13,14].contains(code.size())
    code = code.padLeft(14, '0')
    assert code.size() == 14
    int sum = 0
    code.eachWithIndex{ c, i ->
      sum += c.toInteger() * ((i % 2 == 0) ? 3 : 1)
    }
    returnValue = sum % 10 == 0
  }
  return returnValue
}
Tex
  • 1
  • 1
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()

probably not the most efficient but hope it helps..

Community
  • 1
  • 1
  • It may help if you explain your answer a bit, rather than just writing code so they can learn – winhowes Jan 20 '16 at 18:28
  • sorry about that, im new to this website so didnt really know how to add comments, i found that with the use of # it made the code look more complicated and harder to understand but didnt know how to add a description @winhowes – Ben Russell Nov 15 '16 at 21:52
0

Based on Luciano's answer.

I removed the Linq dependency because I needed it in Java too. Removed padding to 14 digits so it may be used for calculating check digits of longer ITF barcodes.

Codes of uneven length are handled by the (i + code.Length % 2) that makes the 31313 pattern 13131 for codes of uneven length. It avoids creating objects and takes 134 ms (on my PC) to calculate the check digits for all EAN-8 codes.

I know the question was about a IsValidEan() method, but if you a have library with methods you would probably need this one anyway. IsValidEan() should call this for check digit verification after length and other checks.

/// <summary>
/// Calculate check digit for UPC-A, EAN-13, ITF-14 (or any ITF)
/// </summary>
/// <param name="code">This should include the check digit (append a fake check digit if needed)</param>
/// <returns></returns>
public static char CalculateCheckDigit(string code)
{
    // Don't include check digit in the sum (< code.Length - 1)
    int sum = 0;
    for (int i = 0; i < code.Length - 1; i++)
        sum += (code[i] - '0') * (((i + code.Length % 2) % 2 == 0) ? 3 : 1);

    return (char)((10 - (sum % 10)) % 10 + '0');
}

Java version:

public static char CalculateCheckDigit(String code) {
    int sum = 0;
    for(int i = 0; i < code.length() - 1; i++)
        sum += ((int)code.charAt(i) - (int)'0') * (((i + code.length() % 2) % 2 == 0) ? 3 : 1);

    return (char)((10 - (sum % 10)) % 10 + (int)'0');
}
Greenonline
  • 1,330
  • 8
  • 23
  • 31
0

I wrote this Python code to validate UPC codes quickly and succinctly:

def upc_validate(upc):
    # check if upc is exactly 12 digits
    upc = str(upc)
    if len(upc) != 12 or len(list(filter(lambda x: 48 <= ord(x) <= 57, upc))) != 12:
        return False

    # validate CRC / check digit (sum of digits is a product of 10 after multiplying each odd digit by CRC)
    digits = list(map(int, upc))
    crc = digits[-1]
    total = sum([digit if i & 1 else digit * crc for i, digit in enumerate(digits)])
    return total % 10 == 0
cdb
  • 11
  • 3
0

I spent some time today to fully support checksum calculation for GTIN-8, GTIN-12, GTIN-13 and GTIN-14. Most algorithm examples only supports GTIN-13 or are ugly.

What about this?

public static char CalculateCheckSumDigit(string code)
{
   // https://www.gs1.org/services/how-calculate-check-digit-manually
   var reversed = code.Reverse().Skip(1);

   int sum = 0;
   int multiplier = 3;
   foreach(char c in reversed)
   {
       if (multiplier == 3)
       {
           sum += (c - 0x30) * multiplier;
           multiplier = 1;
       }
       else
       {
           sum += (c - 0x30) * multiplier;
           multiplier = 3;
       }
   }

   int closestTenth = (sum + 9) / 10 * 10;

   return (char)((closestTenth - sum) + 0x30);
}
Mange
  • 1
0

In case anyone needs a javascript version of AlexDev's answer, here goes. I noticed AlexDev's answer also works with ITF barcodes, where some other answers do not.

function isValidGtin(barcode) {
    var code = "00000" + barcode;
    code = code.substring(code.length - 14);
    var sum = 0;
    for (var i = 0; i < code.length; i++) {
        sum += code[i] * ((i % 2 == 0) ? 3 : 1);
    }
    return (sum % 10) == 0;
}
mike nelson
  • 21,218
  • 14
  • 66
  • 75
-2

In Clipper/Harbour for EAN8,EAN12,EAN13,EAN14

function DigitEan(cCodigo)
    local cCode := AllTrim(cCodigo)
    local nLen := Len(cCode)
    local cc := 0
    local i := 0

    for i:=1 to nLen-1
        cc+=(asc(substr(cCode,nLen-i,1))-48)*iif(i%2==0,1,3)
    next

return ( (10-(cc%10))%10 )
  • 1
    Please answer only in the language the OP asks for. – General Grievance Oct 20 '20 at 13:39
  • There are already answers in multiple languages for this question. If it illustrates a different approach or algorithm, it can still add value even if it's different syntax. The only problem with this answer is that there is no explanation of what's happening here or why. Code by itself isn't quite enough. – Mnebuerquo Jun 11 '21 at 12:51
-4
private void button1_Click(object sender, EventArgs e)
{
    string code = textBox1.Text;
    string sBarcode = string.Empty;
    sBarcode = IsValidGtin(code);
    lblBarCode.Text = sBarcode;
}
public static string 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 2:
            code = code + "000000000";
            break;
        case 3:
            code = code + "00000000";
            break;
        case 4:
            code = code + "0000000";
            break;
        case 5:
            code = code + "000000";
            break;
        case 6:
            code = code + "00000";
            break;
        case 7:
            code = code + "0000";
            break;
        case 8:
            code = code + "000";
            break;
        case 9:
            code = code + "00";
            break;
        case 10:
            code = code + "0";
            break;
        case 11:
            break;
        case 12:
            code = code.Substring(0, 11);
            break;
        //default:
        // wrong number of digits
        //  return false;
    }
    // calculate check digit
    int[] a = new int[12];
    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];
    string check = Convert.ToString((10 - (sum % 10)));
    // evaluate check digit
    // int last = int.Parse(code[13].ToString());
    // return check == last;
    code = code + check;
    return code;
}
Adi Lester
  • 24,731
  • 12
  • 95
  • 110
Harsh
  • 1
  • 2
    This code is a very poor approach to the solution of the problem. It could only be used if the programmer does not have access to loop structures, for some reason. The comment "pad with zeros to lengthen to 14 digits" is inconsistent with the code that pads to 11 and, the bulk of EAN codes is expected to have 13 digits, anyway. I do not know what @Adi Lester edited but this code does not provide a correct solution to the problem. – ThunderGr Sep 26 '13 at 06:20
  • @ThunderGr I merely fixed the answer's formatting. You can see the revision history by clicking the "edited..." link – Adi Lester Sep 26 '13 at 09:53