Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

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?

share|improve this question
2  
\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
1  
The numeric value of 1 is 1, of course. –  egreg Dec 31 '11 at 14:01
2  
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
2  
@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
show 5 more comments

3 Answers 3

up vote 7 down vote accepted

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
        \addtocounter{checksum}{-11}%
    \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)}%
        \addtocounter{weight}{-1}%
        \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.

share|improve this answer
    
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
add comment
\documentclass{article}
\usepackage{xstring}
\def\GOODISBN#1{ISBN #1 is valid}
\def\BADISBN#1{ISBN #1 is invalid}

\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
      \BADISBN{\ISBN@arg}
    \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
    {\advance\@tempcnta\m@ne
     \@tempcntb=\numexpr\@tempcntb+\next*\@tempcnta\relax
    }
  \@tempcnta=\@tempcntb
  \divide\@tempcnta by 11
  \multiply\@tempcnta by 11
  \advance\@tempcntb-\@tempcnta
  \@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
    \BADISBN{\ISBN@arg}
  \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
    {\advance\@tempcnta\@ne
     \@tempcntb=\numexpr\@tempcntb+\next*\ifodd\@tempcnta 1\else 3\fi\relax
    }
  \@tempcnta=\@tempcntb
  \divide\@tempcnta by 10
  \multiply\@tempcnta by 10
  \advance\@tempcntb-\@tempcnta
  \@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
    \BADISBN{\ISBN@arg}
  \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   
    {\advance\@tempcnta\m@ne
     \@tempcntb=\numexpr\@tempcntb+\if\next X10\else\next\fi*\@tempcnta\relax
    }
  \@tempcnta=\@tempcntb
  \divide\@tempcnta by 11
  \multiply\@tempcnta by 11 
  \advance\@tempcntb-\@tempcnta
  \ifnum\@tempcntb=\z@
    \GOODISBN{\ISBN@arg}
  \else
    \BADISBN{\ISBN@arg}
  \fi
}
share|improve this answer
add comment

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
         return tostring(temp)
      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}
share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.