# Calculating checksum

I would like to calculate a checksum in LaTeX. My problem is that I don't know how to get the numeric value of a char.

Here is the pseudo code of the algorithm:

var input = "123456789";
car output = "";
var checksum = 0;
var weight = 10;

foreach(Char c in input) {
checksum += Char.GetNumericValue(c) * weight;
weight--;
}

checksum = 11-(checksum mod 11);
if(checksum == 10)
output += "X";
else if (checksum == 11)
output += "0";
else
output += checksum;

print output;


Actually I tried this using the packages forloop and xstring:

\newcommand{\inputstr}{123456789}
\newcounter{i}
\newcounter{c}
\forloop{i}{1}{\value{i} < 10}{%
%\StrChar{\inputstr}{\value{i}} % returns one char
\setcounter{c}{\value{\StrChar{\inputstr}{\value{i}}}} % here is no convertion :(
}


Multiply and addition should be also no problem with \multiply and \addcounter but how to do the modulo operation?

-
\number\A will give you the character number. –  Yiannis Lazarides Dec 31 '11 at 13:45
@YiannisLazarides: Why not useing <char> directly? It already gives the ASCII number. –  Martin Scharrer Dec 31 '11 at 13:52
The numeric value of 1 is 1, of course. –  egreg Dec 31 '11 at 14:01
Is this the ISBN 10 check sum calculation? If so, I suggest to mention it. The Wikipedia article also mentions a simpler algorithm: "While this may seem more complicated than the first scheme, it can be validated simply by adding all the products together then dividing by 11. The sum can be computed without any multiplications by initializing two variables, t and sum, to 0 and repeatedly performing t = t + digit; sum = sum + t; (which can be expressed in C as sum += t += digit;). If the final sum is a multiple of 11, the ISBN is valid." –  Christian Lindig Dec 31 '11 at 14:04
@rekire Now that we know better what you need try the ean13isbn package \usepackage[ISBN=978-80-85955-35-4,SC0]{ean13isbn} and the isbn is EAN13 compatible as well. –  Yiannis Lazarides Dec 31 '11 at 15:19

If you have an argument as an character, i.e. #1=A, then #1 will give you the ASCII number of this character. If you want that the character '0' gives you a numeric value of 0, and so on, you simply have to subtract the value 0 from each value. Luckily then characters for the digits are coded in numeric order in ASCII, i.e. 0-1 = 1 etc.

I would loop over the input text yourself by putting it in front of an end-marker and reading one character a time in a recursive fashion.

\documentclass{article}
\usepackage{calc}

\newcounter{checksum}
\newcounter{weight}
\makeatletter
\newcommand\checksum[1]{%
\setcounter{checksum}{0}%
\setcounter{weight}{10}%
\expandafter\@checksum#1\@nnil
\loop\ifnum\value{checksum}>10
\repeat
\setcounter{checksum}{11-\value{checksum}}%
\ifnum\value{checksum}=10
\def\checksumdigit{X}%
\else
\ifnum\value{checksum}=11
\def\checksumdigit{0}%
\else
\edef\checksumdigit{\arabic{checksum}}%
\fi\fi
\checksumdigit
}
% Reads the input one token a time, should only contains normal characters!
\def\@checksum#1{%
\ifx\@nnil#1\relax\else % stop looping when endmarker is read
\addtocounter{checksum}{\value{weight}*(#1-0)}%
\expandafter\@checksum % Recursive call => loop
\fi
}
\makeatother

\begin{document}

\checksum{383480757}%5

\checksum{055215295}%1

\checksum{020113448}%9

\end{document}


This stores the checksum digit into \checksumdigit and prints it in the text. I tested it successfully on the three books above.

-
Of course this doesn't work with ISBN-13, where the weights are alternatively 1 and 3. –  egreg Dec 31 '11 at 15:40
@egreg: Is ISBN-13 than always equal to the EAN? You could change the \addtocounter{weight}{-1} to \ifnum\value{weight}=1 \setcounter{weight}{3}\else\setcounter{weight}{1}\fi easily, and change the init value of course. Ok, you don't have an automatic detection, but this was not asked for in the question. I simply gave him the code he asked for. –  Martin Scharrer Dec 31 '11 at 15:45
An ISBN-10 can be converted to ISBN-13 by adding 978 in front and recomputing the checksum. –  egreg Dec 31 '11 at 15:47
@egreg: I thought so, that's actually a general EAN code with the normal ISBN code (i.e. the 10digit one) integrated. Funny that they now gave it an own name, it has been around like this for longer. –  Martin Scharrer Dec 31 '11 at 15:49
\documentclass{article}
\usepackage{xstring}
\def\GOODISBN#1{ISBN #1 is valid}

\makeatletter
\def\checkISBN#1{%
\def\ISBN@arg{#1}%
\StrDel{#1}{-}[\ISBN@temp]%
\expandafter\StrLen\expandafter{\ISBN@temp}[\ISBN@length]%
\ifnum\ISBN@length=10
\expandafter\checkISBNold\expandafter{\ISBN@temp}%
\else
\ifnum\ISBN@length=13
\expandafter\checkISBNnew\expandafter{\ISBN@temp}%
\else
\fi
\fi}

\def\checkISBNold#1{%
\StrGobbleRight{#1}{1}[\ISBN@temp]%
\StrRight{#1}{1}[\ISBN@check]%
\@tempcnta=11 \@tempcntb=\z@
\expandafter\@tfor\expandafter\next
\expandafter:\expandafter=\ISBN@temp\do
\@tempcntb=\numexpr\@tempcntb+\next*\@tempcnta\relax
}
\@tempcnta=\@tempcntb
\divide\@tempcnta by 11
\multiply\@tempcnta by 11
\@tempcntb=\numexpr11-\@tempcntb\relax
\ifnum\@tempcntb=11
\def\ISBN@final{0}%
\else
\ifnum\@tempcntb=10
\def\ISBN@final{X}%
\else
\edef\ISBN@final{\number\@tempcntb}%
\fi
\fi
\ifx\ISBN@final\ISBN@check
\GOODISBN{\ISBN@arg}
\else
\fi
}
\def\checkISBNnew#1{%
\StrGobbleRight{#1}{1}[\ISBN@temp]%
\StrRight{#1}{1}[\ISBN@check]%
\@tempcnta=\z@ \@tempcntb=\z@
\expandafter\@tfor\expandafter\next
\expandafter:\expandafter=\ISBN@temp\do
\@tempcntb=\numexpr\@tempcntb+\next*\ifodd\@tempcnta 1\else 3\fi\relax
}
\@tempcnta=\@tempcntb
\divide\@tempcnta by 10
\multiply\@tempcnta by 10
\@tempcntb=\numexpr10-\@tempcntb\relax
\ifnum\@tempcntb=10
\def\ISBN@final{0}%
\else
\edef\ISBN@final{\number\@tempcntb}%
\fi
\ifx\ISBN@final\ISBN@check
\GOODISBN{\ISBN@arg}
\else
\fi
}

\begin{document}
\checkISBN{1000000011}

\checkISBN{1-00-000001-X}

\checkISBN{0-306-40615-2}

\checkISBN{978-0-306-40615-7}
\end{document}


The result is

ISBN 1000000011 is invalid
ISBN 1-00-000001-X is valid
ISBN 0-306-40615-2 is valid
ISBN 978-0-306-40615-7 is valid

The computation for old ISBN numbers can be streamlined:

\def\checkISBNold#1{%
\@tempcnta=11 \@tempcntb=\z@
\@tfor\next:=#1\do
\@tempcntb=\numexpr\@tempcntb+\if\next X10\else\next\fi*\@tempcnta\relax
}
\@tempcnta=\@tempcntb
\divide\@tempcnta by 11
\multiply\@tempcnta by 11
\ifnum\@tempcntb=\z@
\GOODISBN{\ISBN@arg}
\else
\fi
}

-

An other solution with luatex, based on what @egreg provided.

\begin{filecontents*}{isbn.lua}
function checksum(str)
local temp = 0
local weight = 10
for i = 1, string.len(str) do
local c = str:sub(i,i)
temp = temp + tonumber(c) * weight
weight = weight - 1
end
temp = 11 - (temp % 11)
if temp == 10 then
return "X"
else
if temp == 11 then
return "0"
else
end
end
end

function checkISBN(str)
local ISBN
local ISBN_test_str = "ISBN " .. str .. " is "
ISBN = str:gsub("-","")
if string.len(ISBN) == 10 then
return tex.sprint(ISBN_test_str .. checkISBNold(ISBN))
else
if string.len(ISBN) == 13 then
return tex.sprint(ISBN_test_str .. checkISBNnew(ISBN))
else
return tex.sprint(ISBN_test_str .. "invalid")
end
end
end

function checkISBNold(str)
local check = str:sub(-1)
local computedcheck = tostring(checksum(str:sub(1,string.len(str)-1)))
if check == computedcheck then
return "valid"
else
return "invalid"
end
end

function checkISBNnew(str)
local check = str:sub(-1)
local ISBN = str:sub(1,string.len(str)-1)
local temp = 0
local weight,computedcheck
for i = 1, string.len(ISBN) do
local c = str:sub(i,i)
if i%2 == 1 then
weight = 1
else
weight = 3
end
temp = temp + tonumber(c) * weight
end
temp = temp % 10
if temp == 0 then
computedcheck = "0"
else
computedcheck = tostring(10 - temp)
end
if check == computedcheck then
return "valid"
else
return "invalid"
end
end
\end{filecontents*}

\documentclass{minimal}
\directlua{dofile("isbn.lua")}
\def\checksum#1{%
\directlua{tex.sprint(checksum("#1"))}}
\def\checkISBN#1{%
\directlua{checkISBN("#1")}}
\begin{document}
\checksum{123456789}

\checkISBN{1000000011}

\checkISBN{1-00-000001-X}

\checkISBN{0-306-40615-2}

\checkISBN{978-0-306-40615-7}

\checkISBN{978-0-306-40415-7}
\end{document}

-