Does a 2-char check digit for a barcode use the first or second char?

1.9k views Asked by At

Based on my understanding of how check digits are supposed to be calculated for barcodes, namely:

0) Sum the values of all the characters at odd indexes (1, 3, etc.)
1) Multiply that sum by 3
2) Sum the values of all the characters at even indexes (o, 2, etc.)
3) Combine the two sums from steps 1 and 2
4) Calculate the check digit by subtracting the modulus 10 of the combined sum from 10

So for example, with a barcode "04900000634" the combined sum is 40*; To get the check sum, the modulus (40 % 10) == 0, and then 10 - 0 == 10.

  • Odd characters == 7; X3 = 21; Even characters == 19, for a combined sum of 40.

Since a check digit is a scalar value, what if the result of the check digit calculation is 10? Does one use "0" or "1"?

Here is the code I'm using (thanks to some help from here: Why does 1 + 0 + 0 + 0 + 3 == 244?); I'm assuming that the formula pseudocoded above applies regardless of the length (8 chars, 12 chars, etc.) and type (128, EAN8, EAN12, etc.) of the barcode.

private void button1_Click(object sender, EventArgs e)
{
    string barcodeWithoutCzechSum = textBox1.Text.Trim();
    string czechSum = GetBarcodeChecksum(barcodeWithoutCzechSum);
    string barcodeWithCzechSum = string.Format("{0}{1}", barcodeWithoutCzechSum, czechSum);
    label1.Text = barcodeWithCzechSum;
}

public static string GetBarcodeChecksum(string barcode)
{
    int oddTotal = sumOddVals(barcode);
    int oddTotalTripled = oddTotal*3;
    int evenTotal = sumEvenVals(barcode);
    int finalTotal = oddTotalTripled + evenTotal;
    int czechSum = 10 - (finalTotal % 10);
    return czechSum.ToString();
}

private static int sumEvenVals(string barcode)
{
    int cumulativeVal = 0;
    for (int i = 0; i < barcode.Length; i++)
    {
        if (i%2 == 0)
        {
            cumulativeVal += Convert.ToInt16(barcode[i] - '0');
        }
    }
    return cumulativeVal;
}

private static int sumOddVals(string barcode)
{
    int cumulativeVal = 0;
    for (int i = 0; i < barcode.Length; i++)
    {
        if (i % 2 != 0)
        {
            cumulativeVal += Convert.ToInt16(barcode[i] - '0');
        }
    }
    return cumulativeVal;
}

UPDATE

The calculator here: http://www.gs1us.org/resources/tools/check-digit-calculator claims that the check digit for 04900000634 is 6

How is that being arrived at?

UPDATE 2

This http://www.gs1.org/barcodes/support/check_digit_calculator revises my understanding of the last part of the equation/formula, where it says, "Subtract the sum from nearest equal or higher multiple of ten = 60- 57 = 3 (Check Digit)"

So, in the case of 04900000634, the combined sum is 40. Based on that formula, the "nearest equal or higher multiple of ten" of 40 is 40, so 40-40=0, and I would expect that to be the check sum (not 6)...so, still confused...

UPDATE 3

I'm not understanding why yet, but mike z's comment must be correct, because when I reverse the "==" and "!=" logic in the sumOddVals() and sumEvenVals() functions, my results correspond to those generated by http://www.gs1us.org/resources/tools/check-digit-calculator

UPDATE 4

Apparently, based on http://en.wikipedia.org/wiki/European_Article_Number, the powers that be behind check digit calculations don't consider the first position to be position 0, but position 1. Confusing for developers, trained to see the first item as residing at index 0, not 1!

3

There are 3 answers

2
Magoo On BEST ANSWER

The check digit is always last.

Starting with the digit immediately to the left of the check digit and moving LEFT, sum each digit, applying a weight of 3 and 1 alternately.

The check digit is then the number which needs to be added to produce a result that is a multiple of 10.

This works for ALL EAN/UPC codes - UPC-E, EAN-8 (which is all valid 8-digit codes except those whoch start 0,6 or 7) UPC-A (12-digit), EAN-13, EAN-14 (sometimes call "TUN" or "Carton" codes) and SSCCs (actually 18-digit, but implemented as part of the EAN128 standard with an AI of '00', misleading some into believing they're 20-digit codes)

When UPC-E was introduced, the original scheme was [language][company][product][check]. 0,6 and 7 were assigned to English and the remainder unassigned. [company] and [product] were variable-length with total 6 digits; short company numbers for companies with many products, long for companies with few products.

EAN used the remainder of the numbers, but assigned [country][company][product][check] where country was 2-digit.

That system soon ran out of puff, but is still occasionally assigned for very small products - and the original products that had numbers before UPC-A/EAN-13 was introduced.

UPC-A used the same schema as UPC-E, but lost the reference to 'language'. 0,6 and 7 were assigned to US/Canada. The company+product was extended to 10 digits.

EAN-13 extended the scheme to 13 digits, 2 for country, 10 for company+product, 1 to check. UPC-A was compatible by prefixing a leading "0".

By implementing the 13-digit scheme, US companies could track each of these codes and UPC-As did not need to be issued on products that already had an EAN-13 assigned. This was scheduled for completion about 8 years ago, but some companies still lag behind.

EAN-14s are used for carton outers. The leading digit is normall referred to as a "Trade Unit Identifier/Number" Hence the entire code is sometimes called a TUN. At first, there was an attempt to codify the leading digit (1=1doz, 2=2doz, etc.) but this was soon abandoned. Most companies use the number as a packaging level (1=cluster of individual items, 2=tray of clusters, 3=box of trays - depending on each company's preference. 9 is reserved. Not a good idea to use 0 (though some companies have) since it produces the same check-digit as the 13-digit code. I've used this for EAN128 codes bearing the batch number on non-retail goods; AI=01;EAN-14 (=EAN13 with TUN=0);AI=10;batch-number.

SSCCs are another can of worms. They're 18-digit - the first digit was originally used as a logistical descriptor, then there's the country-code, manufacturer-code and package-number with a check-digit. Originally, "3" meant an "external" pallet and "4" an "Internal" pallet, but this fell into disuse as impractical as an "Internal" pallet then has to be re-numbered if it gets sent "outside" and vice-versa.

And of course 2-digit country-codes have been supplanted by 3-digit as more countries have adopted the system.

3
Mike Zboray On

There are different weights for different barcode formats. You have described the format for the EAN format - a 1313 weighting. Whereas UPC uses a 3131 weighting scheme. ISBN-10 uses a completely different scheme - the weights are different and the calculation is done modulo 11.

I think the reference you are using is assuming that the digits are indexed starting at 1 not 0. The effect is that you have mixed up odd and even characters. So the sum is 3 x 19 + 7 = 64 and therefore the check digit is 6 not 0. For EAN and UPC, the check digit is the value that must be added to the sum to get a number evenly divisible by 10.

Update

Your description of the check digit algorithm is accurate only for certain classes of EAN barcodes because the weights are aligned such that the last digit is always weighted by 3 (see EAN Number). Therefore, depending on the exact EAN scheme (8,12,13 or 14 digit) odd or even digits are weighted differently.

Thus the proper weights are

0 4 9 0 0 0 0 0 6 3 4
3 1 3 1 3 1 3 1 3 1 3

Giving a sum of 64 and a check digit of 6.

0
B. Clay Shannon-B. Crow Raven On

Based on this: http://www.gs1.org/barcodes/support/check_digit_calculator, barcode calculation formulas can either start with 1, or start with 3, based on whether the ultimate length of the barcode is even (including the checksum val) or add. If the total number of chars, including the checksum, is even, the 1st digit has a weight of three; otherwise (total char count is odd), the 1st digit has a weight of 1. In either case, 3s and 1s alternate, as "13131313..." or "31313131..."

But they always seem to end with a weight of 3; so, it shouldn't matter how long the barcode is, or whether it is odd or even. Simply calculate the value "backwards," assuming the last digit has a weight of 3; HOWEVER, whether the barcode is of even or odd length, that is to say, whether the last digit and those that alternate with it are even or odd makes all the difference in the world, so that has to be noted, too. The "inside" ordinals begin with the penultimate character in the barcode, and skip one backwards; the "outside" ordinals are the last one and then every other one. Anyway, here is the code which, AFAIK, should work to generate and validate/verify check digits for all barcode types:

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

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

UPDATE

With the above code, it is easy enough to add a function to verify that a barcode (with appended checkdigit) is valid:

private static bool isValidBarcodeWithCheckDigit(string barcodeWithCheckDigit)
{
    string barcodeSansCheckDigit = barcodeWithCheckDigit.Substring(0, barcodeWithCheckDigit.Length - 1);
    string checkDigit = barcodeWithCheckDigit.Substring(barcodeWithCheckDigit.Length - 1, 1);
    return GetBarcodeChecksum(barcodeSansCheckDigit) == checkDigit;
}