David Mashiah

Note · Coding theory · Supply chain

A scratched label still scans, and that is not luck

A year moving flowers to shops taught me that a label is a physical object before it is data. It gets wet, creased, dragged across a crate edge. Most of them still scanned, and I assumed the scanner was being clever. It is not. Somebody decided in advance how much of that label they were willing to lose.

A year moving flowers to shops taught me that a label is a physical object before it is data.

It gets wet, because the product it is stuck to has to stay wet. It gets creased when the case is stacked under something heavier. It gets dragged across a crate edge on the way off the truck. By the time a case reached a shop it had been handled six or seven times and the label showed it.

Most of them still scanned. I assumed the scanner was doing something clever with a blurry picture.

It is not. The clever part happened before the label was printed. The symbol is carrying spare parity, deliberately, and how much it carries is a decision somebody made when they chose which kind of symbol to print. Choose one way and a damaged label tells you it is damaged. Choose the other way and it tells you what it was supposed to say.

Those are different products, and most of a supply chain buys the first one without noticing there was a choice.

A check digit only ever says no

Walk the carriers on a single pallet and count what each one can actually do.

The retail unit carries an EAN-13, which ends in one modulo-10 check digit. The shipping case carries an ITF-14, also one modulo-10 check digit. The logistics label on the pallet carries a GS1-128, which is Code 128 underneath, and Code 128 protects itself with a single modulo-103 check character.

Every one of those detects. None of them corrects. A failed check produces the beep that is not the good beep, and the recovery mechanism is a person turning the case around and trying again.

That is a perfectly good design while a person is standing there. It stops being a good design the moment the read is supposed to happen without one, which is the direction every warehouse is moving.

What Reed–Solomon buys instead

Irving Reed and Gustave Solomon published the construction in 1960, in a paper called Polynomial Codes over Certain Finite Fields. The idea is small enough to state in a sentence.

Take your k data symbols and treat them as the coefficients of a polynomial over a finite field. Evaluate that polynomial at n different points and ship all n results. A polynomial of degree less than k is pinned down by any k of its values, so any k symbols that survive are enough to rebuild the original.

You spent n − k symbols on parity. In return you can repair up to (n − k) / 2 damaged symbols when you do not know which ones are damaged, and up to n − k when you do. Reed–Solomon codes are maximum distance separable, meaning their minimum distance is exactly n − k + 1. That is the best any code can do for that overhead, and it is a proved bound rather than a property of somebody's implementation.

The load-bearing word is symbol. The code counts damaged symbols, not damaged bits. Eight bad bits sitting inside one byte cost one symbol. The same eight bad bits spread across eight bytes cost eight.

Physical damage on a label is contiguous. A water drop, a crease, a forklift scrape: each takes out a patch, and a patch maps to a short run of adjacent symbols. Symbol-oriented correction is matched to the way labels actually die. That is not a coincidence, and it is why the 2D symbols use this family rather than bolting a second check digit onto a linear one.

The four levels on a QR code are a commercial decision

QR codes, specified in ISO/IEC 18004, carry Reed–Solomon parity over GF(256): 256 symbols, one byte each. The spec exposes the overhead as a dial with four settings.

LAbout 7 percent of codewords recoverable. The default in most generators, and the wrong default for anything that will be handled.
MAbout 15 percent. The usual compromise for print that stays indoors and flat.
QAbout 25 percent.
HAbout 30 percent. The setting for a label that will get wet, abraded, or partly covered by another label.

Every step up that list buys resilience with data capacity. More parity in a symbol of fixed size means fewer bytes of payload; the same payload at a higher level means a physically larger symbol. On a sticker that has to fit on the side of a wet bucket, that is a real trade with a real cost, and it is usually made by whoever configured the label printer rather than by anyone who has watched the cases come back.

There is a detail in the QR spec worth borrowing. The field that records which correction level was used is itself protected, by a separate (15, 5) BCH code. The metadata has to survive before the payload can be decoded at all, so it gets its own code sized for its own job. The layering is deliberate.

Data Matrix takes the other approach. Its current version, ECC 200 in ISO/IEC 16022, does not offer a dial: parity is fixed per symbol size. GS1 DataMatrix, which is GS1's own profile of that symbology and is spelled as one word to distinguish it, is what pharmaceutical serialisation runs on under the European Falsified Medicines Directive. That is a good sanity check on all of the above. A pack marking that has to stay readable through distribution, storage and handling all the way to a dispensing counter is a 2D symbol with real correction, not a linear one with a check digit.

RFID is a detection story, and I have been sloppy about this

The shorthand you hear, and that I have used loosely myself, is that error-correcting codes are what make barcodes and RFID reliable. The first half is right. The second half is not, and the difference matters operationally.

EPC UHF Gen2, standardised as ISO/IEC 18000-63, protects its air-interface messages with cyclic redundancy checks: a CRC-5 on the query, a CRC-16 on the packet. A CRC detects. It does not repair.

The recovery mechanism there is retransmission. A read that fails its check is thrown away and the interrogator asks again, and because it is asking hundreds of times a second, throwing one away is nearly free.

So the honest version is that the 2D symbols on your cases repair damage, and the RFID layer detects damage and gets another go. Both hand you an identifier you can act on. They fail differently, though, and the shorthand hides exactly the way the second one fails: it depends on there being another chance. A tag that passes through the field once, at speed, at a bad angle, behind a pallet of something wet, does not get another chance. There is no parity waiting to reconstruct what that read should have been.

Why I built one over 32 symbols instead of 256

Barcodes work over GF(256) because their symbol is a byte, and a byte is what the payload already is. That is inheritance, not optimisation.

The toolkit I published works over GF(25), which is 32 symbols, because the data was not bytes. It was sequence data over a small alphabet: four nucleotides, or twenty amino acids. Forcing a twenty-letter alphabet through byte-sized arithmetic leaves most of the field unused and makes every parity symbol cost far more than the information it is protecting.

GF(32) holds a twenty-letter alphabet in one symbol with room to spare. Same construction, same distance bound, field size chosen by the alphabet rather than inherited from a machine word.

That is the part that transfers, and it is the reason I think this belongs on a site about distribution rather than only in a repository. Reed–Solomon is not a barcode feature. It is a parameterised construction with two questions attached: what is my alphabet, and what does my damage look like? Answer those honestly and the parameters follow. Answer them by accepting a library default and you have made the decision anyway, just without knowing what you chose.

Both outputs are open: the encoding work at https://doi.org/10.6084/m9.figshare.32885588 and the toolkit with the error correction in it at https://doi.org/10.6084/m9.figshare.32963456.

What none of it fixes

Correction protects a symbol between the printer and the scanner. It has no opinion whatsoever about whether the number was right when it was printed.

The failure I actually saw on a loading bay was almost never a corrupted barcode. It was a case wearing the correct, intact, perfectly readable label for the wrong contents. Or a pallet built from a pick list that had been amended out loud and never written down. Or two orders for the same shop consolidated by someone who was right about it, and one order later that was moved by someone who was not.

Parity will reconstruct a damaged wrong identifier into a pristine wrong identifier and hand it over with no flag on it at all. The confidence is the dangerous part. A clean scan reads as a settled question, and downstream nothing ever asks it again.

Data integrity and data correctness are separate properties. A chain can have solved the first one completely, all the way down to proved distance bounds on the symbols, and still be wrong about what is on the truck. The mathematics is silent on that, and it is the more expensive of the two problems.

The scratched label scanning is not luck. It is somebody's decision about how much of the symbol they were prepared to lose, made once, upstream, probably by default. Whether the number on it was ever true is a different question, and nothing in the code answers it.

The GF(25) encoding and Reed–Solomon toolkit this note draws on is open: https://doi.org/10.6084/m9.figshare.32963456. This page at davidmashiah.com is the canonical version.