MOD10

MOD10 er forkortelse for Modulus 10-algoritmen, også kalt Luhn-algoritmen etter oppfinneren Hans Peter Luhn. Modulus-10 algoritmen benyttes blant annet som beregningsmetode for et kontrollsiffer i KID-numre på bankenes innbetalingsblanketter.

Et tenkt KID-nummer er 2345676. Det siste sifferet i KID-nummeret er et kontrollsiffer. I eksempelet over er dette sifferet 6. KID-nummeret uten kontrollsiffer er 234567.

Beregning av kontrollsiffer med Modulus 10-algoritmen 
Hvert siffer i eksempelet over multipliseres med vekttallene 2, 1, 2, 1 osv., regnet fra høyre mot venstre.

 7 × 2 = 14 ; 1 + 4 = 5
 6 × 1 = 6
 5 × 2 = 10 ; 1 + 0 = 1
 4 × 1 = 4
 3 × 2 = 6
 2 × 1 = 2

Tverrsummen er 5 + 6 + 1 + 4 + 6 + 2 = 24.

I Modulus10 adderer man ikke produktene, men de enkelte siffer i produktene. Produktet 14 gir altså bidraget 1 + 4 til tverrsummen. Entallsifferet i tverrsummen (i dette tilfellet 4 [resultatet av (24 % 10)]) trekkes fra 10 og resultatet blir kontrollsifferet (i dette tilfellet 6). Dersom resultatet av dette blir 10, blir kontrollsifferet 0.

 10 – 4 = kontrollsifferet 6

Komplett og gyldig KID-nummer i dette eksempelet er derfor 2345676.

Bakgrunn 
Kontrollsifferet skal avdekke om det er gjort inntastingsfeil eller feil ved OCR-lesing. Denne algoritmen er best på å avdekke gale enkeltsifre, da ett enkelt siffer feil alltid vil gi et annet kontrollsiffer. Dersom flere sifre er feil, vil sjansene øke for at kontrollsifferet regnes ut til det samme og at feilen ikke oppdages. Om en tilfeldig tallrekke kontrolleres, er sjansen 10 % for at siste siffer stemmer med kontrollsiffer-algoritmen.

Denne algoritmen er også designet til å avdekke en ting til: Dersom to siffer har byttet plass, vil kontrollsifferet bli et annet. Derfor brukes vekttallene 21212121.. og så videre Denne typen feil er det noe økt sjanse for ved inntasting av lange KID-nummer.

Når det gjelder KID-nummer, kontrolleres også antall siffer det skal være, slik at et ekstra siffer eller et siffer for lite også gir feil KID.

Dersom et feil KID-nummer skulle slippe gjennom, er det to muligheter for mottakeren: Enten er KID-nummeret et annet gyldig nummer som er utstedt på en annen faktura, slik at betalingen knyttes til feil kunde, noe som kan være vanskelig å oppdage, og kan resultere i feil tilbakebetalinger. Den andre muligheten er at KID-nummeret ikke er tatt i bruk eller er en ugyldig KID. Dette vil mottakeren kunne avdekke, og vil kunne kontrollere nærmere hvem som har betalt manuelt. Jo kortere KID-nummer som benyttes (relativt til antall gyldige fakturaer), jo mer sannsynlig er det at feil som slipper gjennom resulterer i et annet benyttet nummer.

Lange KID-nummer som inneholder både kundenummer og fakturanummer regnes som meget sikre, siden feil i den ene eller den andre delen nesten alltid vil kunne oppdages. At feil i begge delene tilfeldigvis matcher en annens kundenummer/fakturanummer-kombinasjon er svært usannsynlig.

Implementasjoner i forskjellige programmeringspråk

PHP 

$kid_u = "180986";
$kontrollsiffer = mod10($kid_u);
echo $kid_u . $kontrollsiffer;

function mod10($kid_u) {
	$siffer = str_split(strrev($kid_u));
	$sum = 0;
	for($i = 0; $i < count($siffer); ++$i) 
		$sum += ( $i & 1 ) ? $siffer[$i] : tverrsum($siffer[$i] * 2);

	$sum = (($sum == 0) ? 0 : 10 - substr($sum, -1));
	if($sum == 10)
		$sum=0;
	return $sum;
}

function tverrsum($tall) {
	return array_sum(str_split($tall));
}

Python 
def mod10(kidnummer:int) -> int:
    tverrsum = 0
    faktor = 2 
    # Gå gjennom tallene i KID-nummeret i motsatt rekkefølge
    for siffer in reversed(str(kidnummer)):
        # Legg til bidraget av siffer ganget med faktor i tverrsummen
        tverrsum += sum(int(j) for j in str(int(siffer) * faktor))
        # Regn ut ny faktor. Rekkefølgen er 2, 1, 2, 1, 2, 1, ...
        faktor = faktor % 2 + 1
    return (10 - tverrsum % 10) % 10

Haskell 
kid10 :: [Int] -> [Int]
kid10 nr = nr ++ [ctrl $ sum (map tv2 multi)]
    where tv2 t = t `div` 10 + t `mod` 10
        multi = zipWith (*) (reverse nr) (cycle [2,1])
        ctrl k = (10 - (k `mod` 10)) `mod` 10

C# 
//USAGE: Mod10("502114008060") --> 8. The controlnumber is 8. 
public static int Mod10(string kid)
        {
            bool isOne = false;
            int controlNumber = 0;
            foreach (char number in kid.Reverse())
            {
                var intNumber = Int32.Parse(number.ToString());
                var sum = isOne ? intNumber : 2 * intNumber;
                if (sum > 9)
                {
                    sum = (sum%10) + 1;
                }
                isOne = !isOne;
                controlNumber += sum;
            }
            return (10 - (controlNumber % 10)) % 10 == 0 ? 0 : 10 - (controlNumber % 10) ;
        }

Kotlin 
fun validMod10(cid: String): Boolean {
    return cid.toBigIntegerOrNull()?.let {
        var checksum: Int = 0

        for (i in cid.length - 1 downTo 0 step 2) {
            checksum += cid[i] - '0'
        }
        for (i in cid.length - 2 downTo 0 step 2) {
            val n: Int = (cid[i] - '0') * 2
            checksum += if (n > 9) n - 9 else n
        }

        return checksum%10 == 0
    } ?: false
}

Matlab 
function checkNum = mod10CheckSum(num) 
	if length(num) == 1
		numVec = fliplr(num2vec(num)); 
	else
		numVec = fliplr(num); 
	end

	mult = 2.*ones(1,length(numVec)); 
	mult(2:2:length(numVec)) = mult(2:2:length(numVec)) ./ 2; 
	sumOfProd = sum( numVec .* mult ); 
	checkNum = mod( 10 - mod( sumOfProd,10 ) , 10 ); 

end % mod10CheckSum

%-------------------------------------------------------
function vec = num2vec(num)
% Hvis du ikke har laget en slik funksjon fra før av. Gjør om tall til vektor

maxPow = length(num2str(num));

vec = zeros(1,maxPow);
pow = maxPow - 1;

for i = 1:maxPow
    vec(i) = floor( num/(10^pow) );
    num = num - vec(i)*10^pow;
    pow = pow - 1;
end

end % num2vec

Javascript 
/* luhn 10 aka MOD10
 * @v     : input number as string
 * @check : [true | false] Whether or not to get or check
 *          the given number.                         Default false
 * @debug : [true | false] Print debug information.   Default false
 *
 * Return boolean if check, else check digit.
 * ****************************************************************/
function luhn10 (v, check, debug) {
    let r, i,
        a = v.replace(/[^\d]/g, '').split(''),
        s = 0,
        m = check ? 1 : 0
    ;
    for (i = a.length - 1; i >= 0; --i) {
        if ((m ^= 1))
            if ((a[i] *= 2) > 9)
                a[i] -= 9;
        s += +a[i];
    }
    if ((r = (10 - s % 10)) === 10)
        r = 0;
    if (debug) {
        console.log(
            `${v} => ${s} % 10 -> ${r} `,
            `${check ? (r === 0 ? 'OK' : 'BAD') : ''}`
        );
    }
    return check ? r === 0 : r;
}

// Example:
// get check digit
result = luhn10 ('62512482'); 
// check value
result = luhn10 ('625124821', true);

Se også
 MOD11

Kryptografi