ISBN13 check digit

Revision as of 06:14, 16 January 2022 by Not a robot (talk | contribs) (Add CLU)

Validate the check digit of an ISBN-13 code:

Task
ISBN13 check digit
You are encouraged to solve this task according to the task description, using any language you may know.
Task
  •   Multiply every other digit by  3.
  •   Add these numbers and the other digits.
  •   Take the remainder of this number after division by  10.
  •   If it is  0,   the ISBN-13 check digit is correct.


Use the following codes for testing:

  •   978-1734314502       (good)
  •   978-1734314509         (bad)
  •   978-1788399081       (good)
  •   978-1788399083         (bad)


Show output here, on this page


See also



11l

Translation of: Python

<lang 11l>F is_isbn13(=n)

  n = n.replace(‘-’, ‘’).replace(‘ ’, ‘’)
  I n.len != 13
     R 0B
  V product = sum(n[(0..).step(2)].map(ch -> Int(ch)))
            + sum(n[(1..).step(2)].map(ch -> Int(ch) * 3))
  R product % 10 == 0

V tests = |‘978-1734314502

           978-1734314509
           978-1788399081
           978-1788399083’.split("\n")

L(t) tests

  print(‘ISBN13 ’t‘ validates ’is_isbn13(t))</lang>
Output:
ISBN13 978-1734314502 validates 1B
ISBN13 978-1734314509 validates 0B
ISBN13 978-1788399081 validates 1B
ISBN13 978-1788399083 validates 0B

8080 Assembly

<lang 8080asm> org 100h jmp demo ;;; --------------------------------------------------------------- ;;; Check if the string at BC is a valid ISBN-13 code. ;;; Carry set if true, clear if not. isbn13: lxi h,0 ; HL = accumulator mov d,h ; D = 0 (such that if E=A, DE=A). call isbngc ; Get first character rnc ; Carry clear = invalid dad d ; Add to running total once call isbngc ; Get second character rnc ; Carry clear = invalid dad d ; Add to running total thrice dad d dad d call isbngc ; Get third character rnc ; Carry clear = invalid dad d ; Add to running total once ldax b ; Fourth character should be a dash '-' inx b cpi '-' stc ; Clear carry w/o touching other flags cmc rnz ; If not equal, invalid. push h ; Keep loop counter on stack mvi l,5 ; 5 times 2 characters isbnlp: xthl ; Accumulator in HL call isbngc ; Get even character jnc isbnex ; If invalid, stop dad d ; Add to running total thrice dad d dad d call isbngc ; Get odd character jnc isbnex ; If invalid, stop dad d ; Add to running total once xthl ; Loop counter in (H)L dcr l ; Done yet? jnz isbnlp ; If not, do next two characters pop h ; Get accumulator lxi d,-10 ; Trial division by ten isbndv: dad d ; Subtract 10 jc isbndv ; Until zero passed mov a,l ; Move low byte to A adi 10 ; Add ten back (the mod loop went one step too far) rz ; If zero, return (carry will have been set) ana a ; Otherwise, make sure carry is clear ret ; And then return isbnex: pop h ; Test failed - throw away accumulator and return ret isbngc: ldax b ; Get character from [BC] inx b ; Increment BC sui '0' ; Subtract ASCII '0' to get digit value cpi 10 ; If 10 or higher (unsigned), invalid digit. mov e,a ; Set (D)E = value ret ;;; --------------------------------------------------------------- ;;; Demo: see if the CP/M command line contains a valid ISBN13 ;;; code. demo: lxi b,82h ; Start of command line argument, skipping first space call isbn13 ; Is it valid? mvi c,9 ; CP/M print string lxi d,good ; If carry is set, then yes jc 5 lxi d,bad ; Otherwise, no. jmp 5 good: db 'good$' bad: db 'bad$'</lang>

Output:
A>isbn13 978-1734314502
good
A>isbn13 978-1734314509
bad
A>isbn13 978-1788399081
good
A>isbn13 978-1788399083
bad

8086 Assembly

<lang asm> cpu 8086 bits 16 org 100h section .text jmp demo isbn13: ;;; --------------------------------------------------------------- ;;; Check if the string at DS:SI is a valid ISBN-13 code. ;;; Carry set if true, clear if false. xor dx,dx ; DX = running total xor ah,ah ; Set AH=0 so that AX=AL call .digit ; Get first digit and add to DX call .digit ; Get second digit and add to DX add dx,ax ; Add to DX twice more add dx,ax call .digit ; Get third digit and add to DX lodsb ; Fourth character should be a '-' cmp al,'-' jne .fail ; If not equal, fail mov cx,5 ; Then loop 5 times for the next 10 digits .loop: call .digit ; Get even digit and add to DX add dx,ax ; Add to running total twice more add dx,ax call .digit ; Get odd digit and add to DX loop .loop ; Do this 5 times mov ax,dx ; Divide running total by 10 mov dl,10 div dl test ah,ah ; Is the remainder zero? jnz .out ; If not, stop (TEST clears carry) stc ; Otherwise, set carry and return ret .digit: lodsb ; Get first character sub al,'0' ; Subtract ASCII 0 to get digit value cmp al,9 ja .dfail add dx,ax ; Add to ASCII ret .dfail: pop dx ; Remove return pointer for .digit from stack .fail: clc ; Failure - clear carry .out: ret ;;; --------------------------------------------------------------- ;;; Demo: see if the MS-DOS command line contains a valid ISBN13 ;;; code. demo: mov si,82h ; Start of command line argument skipping space call isbn13 ; Is it valid? mov ah,9 ; MS-DOS print string mov dx,good ; If carry is set, it is good jc .print mov dx,bad ; Otherwise, it is bad .print: int 21h ret section .data good: db 'good$' bad: db 'bad$'</lang>

Output:
C:\>isbn13 978-1734314502
good
C:\>isbn13 978-1734314509
bad
C:\>isbn13 978-1788399081
good
C:\>isbn13 978-1788399083
bad

Action!

<lang Action!>INCLUDE "D2:CHARTEST.ACT" ;from the Action! Tool Kit

BYTE FUNC CheckISBN13(CHAR ARRAY t)

 BYTE i,index,sum,v
 sum=0 index=0
 FOR i=1 TO t(0)
 DO
   v=t(i)
   IF IsDigit(v) THEN
     v==-'0
     IF index MOD 2=1 THEN
       v==*3
     FI
     sum==+v
     index==+1
   ELSEIF v#'  AND v#'- THEN
     RETURN (0)
   FI
 OD
 IF index#13 OR sum MOD 10#0 THEN
   RETURN (0)
 FI

RETURN (1)

PROC Test(CHAR ARRAY t)

 BYTE correct
 correct=CheckISBN13(t)
 Print(t) Print(" is ")
 IF correct THEN
   PrintE("correct")
 ELSE
   PrintE("incorrect")
 FI

RETURN

PROC Main()

 Put(125) PutE() ;clear screen
 Test("978-1734314502")
 Test("978-1734314509")
 Test("978-1788399081")
 Test("978-1788399083")

RETURN</lang>

Output:

Screenshot from Atari 8-bit computer

978-1734314502 is correct
978-1734314509 is incorrect
978-1788399081 is correct
978-1788399083 is incorrect

Ada

<lang Ada>with Ada.Text_IO;

procedure ISBN_Check is

  function Is_Valid (ISBN : String) return Boolean is
     Odd       : Boolean := True;
     Sum       : Integer := 0;
     Value     : Integer;
  begin
     for I in ISBN'Range loop
        if ISBN (I) in '0' .. '9' then
           Value := Character'Pos (ISBN (I)) - Character'Pos ('0');
           if Odd then
              Sum := Sum + Value;
           else
              Sum := Sum + 3 * Value;
           end if;
           Odd := not Odd;
        end if;
     end loop;
     return Sum mod 10 = 0;
  end Is_Valid;
  procedure Show (ISBN : String) is
     use Ada.Text_IO;
     Valid : constant Boolean := Is_Valid (ISBN);
  begin
     Put (ISBN); Put ("  ");
     Put ((if Valid then "Good" else "Bad"));
     New_Line;
  end Show;

begin

  Show ("978-1734314502");
  Show ("978-1734314509");
  Show ("978-1788399081");
  Show ("978-1788399083");

end ISBN_Check;</lang>

Output:
978-1734314502  Good
978-1734314509  Bad
978-1788399081  Good
978-1788399083  Bad

ALGOL 68

Works with: ALGOL 68G version Any - tested with release 2.8.3.win32

<lang algol68>BEGIN # Check some IsBN13 check digits #

   # returns TRUE if the alledged isbn13 has the correct check sum,           #
   #         FALSE otherwise                                                  #
   #         non-digit characters are ignored and there must be 13 digits     #
   PROC check isbn13 = ( STRING isbn13 )BOOL:
        BEGIN
           INT sum          := 0;
           INT digits       := 0;
           BOOL other digit := FALSE;
           FOR pos FROM LWB isbn13 TO UPB isbn13 DO
               IF CHAR c = isbn13[ pos ];
                  c >= "0" AND c <= "9"
               THEN
                   # have another digit                                       #
                   digits +:= 1;
                   sum    +:= ( ABS c - ABS "0" ) * IF other digit THEN 3 ELSE 1 FI;
                   other digit := NOT other digit
               FI
           OD;
           digits = 13 AND sum MOD 10 = 0
        END; # check isbn13 #
   # task test cases #
   []STRING tests    = ( "978-1734314502", "978-1734314509", "978-1788399081", "978-1788399083" );
   []BOOL   expected = (             TRUE,            FALSE,             TRUE,            FALSE );
   FOR pos FROM LWB tests TO UPB tests DO
       BOOL result = check isbn13( tests[ pos ] );
       print( ( tests[ pos ]
              , ": "
              , IF result THEN "good" ELSE "bad" FI
              , IF result = expected[ pos ] THEN "" ELSE " NOT AS EXPECTED" FI
              , newline
              )
            )
   OD

END</lang>

Output:
978-1734314502: good
978-1734314509: bad
978-1788399081: good
978-1788399083: bad

APL

Works with: Dyalog APL

<lang apl>check_isbn13←{

   13≠⍴n←(⍵∊⎕D)/⍵:0
   0=10|(⍎¨n)+.×13⍴1 3

}</lang>

Output:
      check_isbn13¨ '978-1734314502' '978-1734314509' '978-1788399081' '978-1788399083'
1 0 1 0

AppleScript

Composition of pure functions

<lang applescript>-------------------- ISBN13 CHECK DIGIT --------------------

-- isISBN13 :: String -> Bool on isISBN13(s)

   script digitValue
       on |λ|(c)
           if isDigit(c) then
               {c as integer}
           else
               {}
           end if
       end |λ|
   end script
   
   set digits to concatMap(digitValue, characters of s)
   
   13 = length of digits ¬
       and 0 = sum(zipWith(my mul, digits, cycle({1, 3}))) mod 10

end isISBN13



TEST ---------------------------

on run

   script test
       on |λ|(s)
           {s, isISBN13(s)}
       end |λ|
   end script
   
   map(test, {"978-1734314502", "978-1734314509", ¬
       "978-1788399081", "978-1788399083"})

end run



GENERIC FUNCTIONS ---------------------

-- concatMap :: (a -> [b]) -> [a] -> [b] on concatMap(f, xs)

   set lng to length of xs
   set acc to {}
   tell mReturn(f)
       repeat with i from 1 to lng
           set acc to acc & (|λ|(item i of xs, i, xs))
       end repeat
   end tell
   return acc

end concatMap


-- cycle :: [a] -> Generator [a] on cycle(xs)

   script
       property lng : 1 + (length of xs)
       property i : missing value
       on |λ|()
           if missing value is i then
               set i to 1
           else
               set nxt to (1 + i) mod lng
               if 0 = ((1 + i) mod lng) then
                   set i to 1
               else
                   set i to nxt
               end if
           end if
           return item i of xs
       end |λ|
   end script

end cycle


-- foldl :: (a -> b -> a) -> a -> [b] -> a on foldl(f, startValue, xs)

   tell mReturn(f)
       set v to startValue
       set lng to length of xs
       repeat with i from 1 to lng
           set v to |λ|(v, item i of xs, i, xs)
       end repeat
       return v
   end tell

end foldl


-- isDigit :: Char -> Bool on isDigit(c)

   set n to (id of c)
   48 ≤ n and 57 ≥ n

end isDigit


-- length :: [a] -> Int on |length|(xs)

   set c to class of xs
   if list is c or string is c then
       length of xs
   else
       (2 ^ 29 - 1) -- (maxInt - simple proxy for non-finite)
   end if

end |length|


-- map :: (a -> b) -> [a] -> [b] on map(f, xs)

   -- The list obtained by applying f
   -- to each element of xs.
   tell mReturn(f)
       set lng to length of xs
       set lst to {}
       repeat with i from 1 to lng
           set end of lst to |λ|(item i of xs, i, xs)
       end repeat
       return lst
   end tell

end map


-- min :: Ord a => a -> a -> a on min(x, y)

   if y < x then
       y
   else
       x
   end if

end min


-- mReturn :: First-class m => (a -> b) -> m (a -> b) on mReturn(f)

   -- 2nd class handler function lifted into 1st class script wrapper. 
   if script is class of f then
       f
   else
       script
           property |λ| : f
       end script
   end if

end mReturn


-- mul (*) :: Num a => a -> a -> a on mul(a, b)

   a * b

end mul


-- sum :: [Num] -> Num on sum(xs)

   script add
       on |λ|(a, b)
           a + b
       end |λ|
   end script
   
   foldl(add, 0, xs)

end sum


-- take :: Int -> [a] -> [a] -- take :: Int -> String -> String on take(n, xs)

   set c to class of xs
   if list is c then
       if 0 < n then
           items 1 thru min(n, length of xs) of xs
       else
           {}
       end if
   else if string is c then
       if 0 < n then
           text 1 thru min(n, length of xs) of xs
       else
           ""
       end if
   else if script is c then
       set ys to {}
       repeat with i from 1 to n
           set v to |λ|() of xs
           if missing value is v then
               return ys
           else
               set end of ys to v
           end if
       end repeat
       return ys
   else
       missing value
   end if

end take


-- zipWith :: (a -> b -> c) -> [a] -> [b] -> [c] on zipWith(f, xs, ys)

   set lng to min(|length|(xs), |length|(ys))
   if 1 > lng then return {}
   set xs_ to take(lng, xs) -- Allow for non-finite
   set ys_ to take(lng, ys) -- generators like cycle etc
   set lst to {}
   tell mReturn(f)
       repeat with i from 1 to lng
           set end of lst to |λ|(item i of xs_, item i of ys_)
       end repeat
       return lst
   end tell

end zipWith</lang>

Output:
{{"978-1734314502", true}, {"978-1734314509", false}, {"978-1788399081", true}, {"978-1788399083", false}}

Straightforward

This task can be tackled very simply by working through the numeric text two characters at a time:

<lang applescript>on validateISBN13(ISBN13)

   if (ISBN13's class is not text) then return false
   
   set astid to AppleScript's text item delimiters
   set AppleScript's text item delimiters to {"-", space}
   set ISBN13 to ISBN13's text items
   set AppleScript's text item delimiters to ""
   set ISBN13 to ISBN13 as text
   set AppleScript's text item delimiters to astid
   
   if (((count ISBN13) is not 13) or (ISBN13 contains ".") or (ISBN13 contains ",")) then return false
   try
       ISBN13 as number
   on error
       return false
   end try
   
   set sum to 0
   repeat with i from 1 to 12 by 2
       set sum to sum + (character i of ISBN13) + (character (i + 1) of ISBN13) * 3 -- Automatic text-to-number coercions.
   end repeat
   
   return ((sum + (character 13 of ISBN13)) mod 10 = 0)

end validateISBN13

-- Test: set output to {} set verdicts to {"bad", "good"} repeat with thisISBN13 in {"978-1734314502", "978-1734314509", "978-1788399081", "978-1788399083"}

   set isValid to validateISBN13(thisISBN13)
   set end of output to thisISBN13 & ": " & item ((isValid as integer) + 1) of verdicts

end repeat

set astid to AppleScript's text item delimiters set AppleScript's text item delimiters to linefeed set output to output as text set AppleScript's text item delimiters to astid return output</lang>

Output:
"978-1734314502: good
978-1734314509: bad
978-1788399081: good
978-1788399083: bad"

Or it can be handled purely numerically. Since the "weights" alternate and are palindromic, it makes no difference whether the last digit or the first is treated as the check digit. In fact, if preferred, the repeat below can go round 7 times with the return line as simply: return (sum mod 10 = 0).

<lang applescript>on validateISBN13(ISBN13)

   if (ISBN13's class is not text) then return false
   
   set astid to AppleScript's text item delimiters
   set AppleScript's text item delimiters to {"-", space}
   set ISBN13 to ISBN13's text items
   set AppleScript's text item delimiters to ""
   set ISBN13 to ISBN13 as text
   set AppleScript's text item delimiters to astid
   
   if (((count ISBN13) is not 13) or (ISBN13 contains ".") or (ISBN13 contains ",")) then return false
   try
       set ISBN13 to ISBN13 as number
   on error
       return false
   end try
   
   set sum to 0
   repeat 6 times
       set sum to sum + ISBN13 mod 10 + ISBN13 mod 100 div 10 * 3
       set ISBN13 to ISBN13 div 100
   end repeat
   
   return ((sum + ISBN13) mod 10 = 0)

end validateISBN13</lang>

Arturo

<lang rebol>validISBN?: function [isbn][

   currentCheck: to :integer to :string last isbn
   isbn: map split chop replace isbn "-" "" => [to :integer]
   s: 0
   loop.with:'i isbn 'n [
       if? even? i -> s: s + n
       else -> s: s + 3*n
   ]
   checkDigit: 10 - s % 10
   return currentCheck = checkDigit

]

tests: [

   "978-1734314502" "978-1734314509"
   "978-1788399081" "978-1788399083"

]

loop tests 'test [

   print [test "=>" validISBN? test]

]</lang>

Output:
978-1734314502 => true 
978-1734314509 => false 
978-1788399081 => true 
978-1788399083 => false

AutoHotkey

<lang AutoHotkey>ISBN13_check_digit(n){ for i, v in StrSplit(RegExReplace(n, "[^0-9]")) sum += !Mod(i, 2) ? v*3 : v return n "`t" (Mod(sum, 10) ? "(bad)" : "(good)") }</lang> Examples:<lang AutoHotkey>output := "" nums := ["978-1734314502","978-1734314509","978-1788399081","978-1788399083"] for i, n in nums output .= ISBN13_check_digit(n) "`n" MsgBox % output return</lang>

Output:
978-1734314502	(good)
978-1734314509	(bad)
978-1788399081	(good)
978-1788399083	(bad)

AWK

<lang AWK>

  1. syntax: GAWK -f ISBN13_CHECK_DIGIT.AWK

BEGIN {

   arr[++n] = "978-1734314502"
   arr[++n] = "978-1734314509"
   arr[++n] = "978-1788399081"
   arr[++n] = "978-1788399083"
   arr[++n] = "9780820424521"
   arr[++n] = "0820424528"
   for (i=1; i<=n; i++) {
     printf("%s %s\n",arr[i],isbn13(arr[i]))
   }
   exit(0)

} function isbn13(isbn, check_digit,i,sum) {

   gsub(/[ -]/,"",isbn)
   if (length(isbn) != 13) { return("NG length") }
   for (i=1; i<=12; i++) {
     sum += substr(isbn,i,1) * (i % 2 == 1 ? 1 : 3)
   }
   check_digit = 10 - (sum % 10)
   return(substr(isbn,13,1) == check_digit ? "OK" : sprintf("NG check digit S/B %d",check_digit))

} </lang>

Output:
978-1734314502 OK
978-1734314509 NG check digit S/B 2
978-1788399081 OK
978-1788399083 NG check digit S/B 1
9780820424521 OK
0820424528 NG length

BCPL

<lang bcpl>get "libhdr"

let checkISBN(s) = valof $( let tally = 0

   unless s%0 = 14 resultis false
   unless s%4 = '-' resultis false 
   
   for i=1 to 3
   $(  let digit = s%i-'0'
       test i rem 2 = 0 
           do tally := tally + 3 * digit
           or tally := tally + digit
   $)
   
   for i=5 to 14
   $(  let digit = s%i-'0'
       test i rem 2 = 0
           do tally := tally + digit
           or tally := tally + 3 * digit
   $)
   
   resultis tally rem 10 = 0

$)

let show(s) be

   writef("%S: %S*N", s, checkISBN(s) -> "good", "bad")

let start() be $( show("978-1734314502")

   show("978-1734314509")
   show("978-1788399081")
   show("978-1788399083")

$)</lang>

Output:
978-1734314502: good
978-1734314509: bad
978-1788399081: good
978-1788399083: bad

C

<lang c>#include <stdio.h>

int check_isbn13(const char *isbn) {

   int ch = *isbn, count = 0, sum = 0;
   /* check isbn contains 13 digits and calculate weighted sum */
   for ( ; ch != 0; ch = *++isbn, ++count) {
       /* skip hyphens or spaces */
       if (ch == ' ' || ch == '-') {
           --count;
           continue;
       }
       if (ch < '0' || ch > '9') {
           return 0;
       }
       if (count & 1) {
           sum += 3 * (ch - '0');
       } else {
           sum += ch - '0';
       }
   }
   if (count != 13) return 0;
   return !(sum%10);

}

int main() {

   int i;
   const char* isbns[] = {"978-1734314502", "978-1734314509", "978-1788399081", "978-1788399083"};
   for (i = 0; i < 4; ++i) {
       printf("%s: %s\n", isbns[i], check_isbn13(isbns[i]) ? "good" : "bad");
   }
   return 0;

}</lang>

Output:
978-1734314502: good
978-1734314509: bad
978-1788399081: good
978-1788399083: bad

C++

Translation of: C

<lang cpp>#include <iostream>

bool check_isbn13(std::string isbn) {

   int count = 0;
   int sum = 0;
   for (auto ch : isbn) {
       /* skip hyphens or spaces */
       if (ch == ' ' || ch == '-') {
           continue;
       }
       if (ch < '0' || ch > '9') {
           return false;
       }
       if (count & 1) {
           sum += 3 * (ch - '0');
       } else {
           sum += ch - '0';
       }
       count++;
   }
   if (count != 13) {
       return false;
   }
   return sum % 10 == 0;

}

int main() {

   auto isbns = { "978-1734314502", "978-1734314509", "978-1788399081", "978-1788399083" };
   for (auto isbn : isbns) {
       std::cout << isbn << ": " << (check_isbn13(isbn) ? "good" : "bad") << '\n';
   }
   return 0;

}</lang>

Output:
978-1734314502: good
978-1734314509: bad
978-1788399081: good
978-1788399083: bad

C#

<lang csharp>using System; using System.Linq;

public class Program {

   public static void Main() {
       Console.WriteLine(CheckISBN13("978-1734314502"));
       Console.WriteLine(CheckISBN13("978-1734314509"));
       Console.WriteLine(CheckISBN13("978-1788399081"));
       Console.WriteLine(CheckISBN13("978-1788399083"));
       static bool CheckISBN13(string code) {
           code = code.Replace("-", "").Replace(" ", "");
           if (code.Length != 13) return false;
           int sum = 0;
           foreach (var (index, digit) in code.Select((digit, index) => (index, digit))) {
               if (char.IsDigit(digit)) sum += (digit - '0') * (index % 2 == 0 ? 1 : 3);
               else return false;
           }
           return sum % 10 == 0;
       }
   }

}</lang>

Output:
True
False
True
False

CLU

<lang clu>isbn13_check = proc (s: string) returns (bool)

   if string$size(s) ~= 14 then return(false) end
   if s[4] ~= '-' then return(false) end
   begin
       check: int := 0
       for i: int in int$from_to(1, 14) do
           if i=4 then continue end
           d: int := int$parse(string$c2s(s[i]))
           if i=2 cor (i>4 cand i//2=1) then d := d*3 end
           check := check + d
       end
       return(check//10 = 0)
   end except when bad_format: 
       return(false)
   end

end isbn13_check

start_up = proc ()

   po: stream := stream$primary_output()
   tests: array[string] := array[string]$
      ["978-1734314502",
       "978-1734314509",
       "978-1788399081",
       "978-1788399083"]
       
   for test: string in array[string]$elements(tests) do
       stream$puts(po, test)
       stream$puts(po, ": ")
       if isbn13_check(test)
           then stream$putl(po, "good")
           else stream$putl(po, "bad")
       end
   end

end start_up</lang>

Output:
978-1734314502: good
978-1734314509: bad
978-1788399081: good
978-1788399083: bad

COBOL

Works with: GnuCOBOL

<lang COBOL> ******************************************************************

     * Author: Jay Moseley
     * Date: November 10, 2019
     * Purpose: Testing various subprograms/ functions.
     * Tectonics: cobc -xj testSubs.cbl
     ******************************************************************
      IDENTIFICATION DIVISION.
      PROGRAM-ID. testSubs.
      ENVIRONMENT DIVISION.
      CONFIGURATION SECTION.
      REPOSITORY.
          FUNCTION ALL INTRINSIC
          FUNCTION validISBN13.
      INPUT-OUTPUT SECTION.
      FILE-CONTROL.
      DATA DIVISION.
      FILE SECTION.
      WORKING-STORAGE SECTION.
      01  IX                          PIC S9(4) COMP.
      01  TEST-ISBNS.
          02  FILLER                  PIC X(14) VALUE '978-1734314502'.
          02  FILLER                  PIC X(14) VALUE '978-1734314509'.
          02  FILLER                  PIC X(14) VALUE '978-1788399081'.
          02  FILLER                  PIC X(14) VALUE '978-1788399083'.
      01  TEST-ISBN                   REDEFINES TEST-ISBNS
                                      OCCURS 4 TIMES
                                      PIC X(14).
      PROCEDURE DIVISION.
      MAIN-PROCEDURE.
          PERFORM 
            VARYING IX 
            FROM 1
            BY 1
            UNTIL IX > 4
            DISPLAY TEST-ISBN (IX) '   ' WITH NO ADVANCING
            END-DISPLAY
            IF validISBN13(TEST-ISBN (IX)) = -1
              DISPLAY '(bad)'
            ELSE
              DISPLAY '(good)'
            END-IF
          END-PERFORM.
          GOBACK.
      END PROGRAM testSubs.
     ******************************************************************
     * Author: Jay Moseley
     * Date: May 19, 2016
     * Purpose: validate ISBN-13 (International Standard
     *          Book Number).
     ******************************************************************
      IDENTIFICATION DIVISION.
      FUNCTION-ID. validISBN13.
      ENVIRONMENT DIVISION.
      CONFIGURATION SECTION.
      REPOSITORY.
          FUNCTION ALL INTRINSIC.
      INPUT-OUTPUT SECTION.
      FILE-CONTROL.
      DATA DIVISION.
      FILE SECTION.
      WORKING-STORAGE SECTION.
      01  PASSED-SIZE                 PIC S9(6) COMP-5.
      01  IX                          PIC S9(4) COMP.
      01  WORK-FIELDS.
          02  WF-DIGIT                PIC X.
          02  WF-COUNT                PIC 9(2).
              88  WEIGHT-1  VALUE 1, 3, 5, 7, 9, 11, 13.
              88  WEIGHT-3  VALUE 2, 4, 6, 8, 10, 12.
          02  WF-SUM                  PIC S9(8) COMP.
      LINKAGE SECTION.
      01  PASSED-ISBN                 PIC X ANY LENGTH.
      01  RETURN-VALUE                PIC S9.
      PROCEDURE DIVISION USING PASSED-ISBN
                         RETURNING RETURN-VALUE.
          CALL 'C$PARAMSIZE'
            USING 1
            GIVING PASSED-SIZE
          END-CALL.
      
      COMPUTE-CKDIGIT.
          INITIALIZE WORK-FIELDS.
          PERFORM 
            VARYING IX 
            FROM 1 
            BY 1
            UNTIL IX GREATER THAN PASSED-SIZE
              MOVE PASSED-ISBN (IX:1) TO WF-DIGIT
              IF WF-DIGIT IS NUMERIC
                ADD 1 TO WF-COUNT
                IF WEIGHT-1
                  ADD NUMVAL(WF-DIGIT) TO WF-SUM
                ELSE
                  COMPUTE WF-SUM = WF-SUM + 
                    (NUMVAL(WF-DIGIT) * 3)
                  END-COMPUTE
                END-IF
              END-IF
          END-PERFORM.
          IF MOD(WF-SUM, 10) = 0
            MOVE +0 TO RETURN-VALUE
          ELSE
            MOVE -1 TO RETURN-VALUE
          END-IF.
          GOBACK.
     * - - - - - - - - - - - - - - - - - - - - - - PROGRAM EXIT POINT
      END FUNCTION validISBN13.

</lang>

Output:
978-1734314502   (good)
978-1734314509   (bad)
978-1788399081   (good)
978-1788399083   (bad)

Cowgol

<lang cowgol>include "cowgol.coh";

sub check_isbn13(isbn: [uint8]): (r: uint8) is

   var n: uint8 := 0;
   r := 0;
   loop    
       var c := [isbn];
       isbn := @next isbn;
       if c == 0 then break; end if;
       c := c - '0';
       if c <= 9 then
           r := r + c;
           n := n + 1;
           if (n & 1) == 0 then
               r := r + c * 2;
           end if;
       end if;
   end loop;
   if n == 13 and r%10 == 0 then
       r := 1;
   else
       r := 0;
   end if;

end sub;

var isbns: [uint8][] := {

   "978-1734314502", "978-1734314509", "978-1788399081", "978-1788399083"

};

var result: [uint8][] := {": bad\n", ": good\n"};

var n: uint8 := 0; while n < @sizeof isbns loop

   print(isbns[n]);
   print(result[check_isbn13(isbns[n])]);
   n := n + 1;

end loop;</lang>

Output:
978-1734314502: good
978-1734314509: bad
978-1788399081: good
978-1788399083: bad

D

Translation of: Kotlin

<lang d>import std.stdio;

bool isValidISBN13(string text) {

   int sum, i;
   foreach (c; text) {
       if ('0' <= c && c <= '9') {
           int value = c - '0';
           if (i % 2 == 0) {
               sum += value;
           } else {
               sum += 3 * value;
           }
           i++;
       }
   }
   return i == 13 && 0 == sum % 10;

}

unittest {

   assert(isValidISBN13("978-1734314502"));
   assert(!isValidISBN13("978-1734314509"));
   assert(isValidISBN13("978-1788399081"));
   assert(!isValidISBN13("978-1788399083"));

}</lang>

Excel

LAMBDA

Binding the name ISBN13Check to the following lambda expression in the Name Manager of the Excel WorkBook:

(See LAMBDA: The ultimate Excel worksheet function)

<lang lisp>ISBN13Check =LAMBDA(s,

   LET(
       ns, FILTERP(
           LAMBDA(v,
               NOT(ISERROR(v))
           )
       )(
           VALUE(CHARSROW(s))
       ),
       ixs, SEQUENCE(
           1, COLUMNS(ns),
           1, 1
       ),
       0 = MOD(
           SUM(
               IF(0 <> MOD(ixs, 2),
                   INDEX(ns, ixs),
                   3 * INDEX(ns, ixs)
               )
           ),
           10
       )
   )

)</lang>

and also assuming the following generic bindings in the Name Manager for the WorkBook:

<lang lisp>CHARSROW =LAMBDA(s,

   MID(s,
       SEQUENCE(1, LEN(s), 1, 1),
       1
   )

)


FILTERP =LAMBDA(p,

   LAMBDA(xs,
       FILTER(xs, p(xs))
   )

)


ISDIGIT =LAMBDA(c,

   LET(
       ic, CODE(c),
       AND(47 < ic, 58 > ic)
   )

)</lang>

Output:
fx =ISBN13Check(A2)
A B
1 Candidate string ISBN13 checked
2 978-1734314502 TRUE
3 978-1734314509 FALSE
4 978-1788399081 TRUE
5 978-1788399083 FALSE


Factor

<lang factor>USING: combinators.short-circuit formatting kernel math math.functions math.parser math.vectors qw sequences sequences.extras sets unicode ;

(isbn13?) ( str -- ? )
   string>digits
   [ <evens> sum ] [ <odds> 3 v*n sum + ] bi 10 divisor? ;
isbn13? ( str -- ? )
   "- " without
   { [ length 13 = ] [ [ digit? ] all? ] [ (isbn13?) ] } 1&& ;

qw{ 978-1734314502 978-1734314509 978-1788399081 978-1788399083 } [ dup isbn13? "good" "bad" ? "%s: %s\n" printf ] each</lang>

Output:
978-1734314502: good
978-1734314509: bad
978-1788399081: good
978-1788399083: bad

Forth

The following word not only identifies correct 13 digit ISBN (and EAN) codes, but also 8 digit EAN and GTIN codes. <lang>: is-digit [char] 0 - 10 u< ;

isbn? ( a n -- f)
 1- chars over + 2>r 0 1 2r> ?do
   dup i c@ dup is-digit              \ get length and factor, setup loop
   if [char] 0 - * rot + swap 3 * 8 mod else drop drop then
 -1 chars +loop drop 10 mod 0=        \ now calculate checksum
</lang>
Output:

In Forth, a "true" value is indicated by "-1".

s" 978-1734314502" isbn? .  -1  ok
s" 978-1734314509" isbn? .  0  ok
s" 978-1788399081" isbn? .  -1  ok
s" 978-1788399083" isbn? .  0  ok

Fortran

<lang fortran> program isbn13

   implicit none
   character(len=14), dimension(4), parameter  :: isbns=["978-1734314502", "978-1734314509", "978-1788399081", "978-1788399083"]
   integer                                     :: i
   do i = 1, ubound(isbns, 1)
       if (check_isbn13(isbns(i))) then
           print*, isbns(i), " : ", "good"
       else
           print*, isbns(i), " : ", "bad"
       end if
   end do

contains

   pure function check_isbn13(isbn)
       character(len=*), intent(in)    :: isbn
       logical                         :: check_isbn13
       integer                         :: summ, counter, i, digit
       check_isbn13 = .false.
       counter = 0
       summ = 0
       do i = 1, len(isbn)
           if (isbn(i:i) == ' ' .or. isbn(i:i) == '-') cycle
           counter = counter + 1
           read(isbn(i:i), '(I1)') digit
           if (modulo(counter, 2) == 0) then
               summ = summ + 3*digit
           else
               summ = summ + digit
           end if
       end do
       if (counter == 13 .and. modulo(summ, 10) == 0) check_isbn13 = .true.
   end function check_isbn13

end program isbn13 </lang>

Output:
 978-1734314502 : good
 978-1734314509 : bad
 978-1788399081 : good
 978-1788399083 : bad

FreeBASIC

<lang freebasic>#define ZEROC asc("0")

function is_num( byval c as string ) as boolean

   if asc(c) >= ZEROC andalso asc(c)<ZEROC+10 then return true
   return false

end function

function is_good_isbn( isbn as string ) as boolean

   dim as uinteger charno = 0, digitno = 0, sum = 0
   dim as string*1 currchar
   while charno <= len(isbn)
       currchar = mid(isbn,charno,1)
       if is_num(currchar) then
           if digitno mod 2 = 1 then
               sum += 2*(asc(currchar)-ZEROC)
           end if
           sum += asc(currchar)-ZEROC
           digitno += 1
       end if
       charno += 1
   wend
   if sum mod 10 = 0 then 
       return true
   else 
       return false
   end if

end function

dim as string isbns(0 to 3) = { "978-1734314502", "978-1734314509", "978-1788399081", "978-1788399083" } dim as uinteger i for i = 0 to 3

   if is_good_isbn( isbns(i) ) then
       print isbns(i)+": good"
   else
       print isbns(i)+": bad"
   end if

next i</lang>

Output:
978-1734314502: good
978-1734314509: bad
978-1788399081: good
978-1788399083: bad

F#

<lang fsharp>// ISBN13 Check Digit open System

let parseInput (input: string) =

   Seq.map(fun c -> c |> string) input |> Seq.toList |> List.map(fun x -> Int32.Parse x)

[<EntryPoint>] let main argv =

   let isbnnum = parseInput (String.filter (fun x -> x <> '-') argv.[0])
   // Multiply every other digit by 3
   let everyOther = List.mapi (fun i x -> if i % 2 = 0 then x * 3 else x) isbnnum
   // Sum the *3 list with the original list
   let sum = List.sum everyOther + List.sum isbnnum
   // If the remainder of sum / 10 is 0 then it's a valid ISBN13
   if sum % 10 = 0 then
       printfn "%s Valid ISBN13" argv.[0]
   else
       printfn "%s Invalid ISBN13" argv.[0]
   0</lang>
Output:
978-1734314502 Valid ISBN13
978-1734314509 Inalid ISBN13
978-1788399081 Valid ISBN13
978-1788399083 Inalid ISBN13

Fōrmulæ

Fōrmulæ programs are not textual, visualization/edition of programs is done showing/manipulating structures but not text. Moreover, there can be multiple visual representations of the same program. Even though it is possible to have textual representation —i.e. XML, JSON— they are intended for storage and transfer purposes more than visualization and edition.

Programs in Fōrmulæ are created/edited online in its website, However they run on execution servers. By default remote servers are used, but they are limited in memory and processing power, since they are intended for demonstration and casual use. A local server can be downloaded and installed, it has no limitations (it runs in your own computer). Because of that, example programs can be fully visualized and edited, but some of them will not run if they require a moderate or heavy computation/memory resources, and no local server is being used.

In this page you can see the program(s) related to this task and their results.

Go

<lang go>package main

import (

   "fmt"
   "strings"
   "unicode/utf8"

)

func checkIsbn13(isbn string) bool {

   // remove any hyphens or spaces
   isbn = strings.ReplaceAll(strings.ReplaceAll(isbn, "-", ""), " ", "")
   // check length == 13
   le := utf8.RuneCountInString(isbn)
   if le != 13 {
       return false
   }
   // check only contains digits and calculate weighted sum
   sum := int32(0)
   for i, c := range isbn {
       if c < '0' || c > '9' {
           return false
       }
       if i%2 == 0 {
           sum += c - '0'
       } else {
           sum += 3 * (c - '0')
       }
   }
   return sum%10 == 0

}

func main() {

   isbns := []string{"978-1734314502", "978-1734314509", "978-1788399081", "978-1788399083"}
   for _, isbn := range isbns {
       res := "bad"
       if checkIsbn13(isbn) {
           res = "good"
       }
       fmt.Printf("%s: %s\n", isbn, res)
   }

}</lang>

Output:
978-1734314502: good
978-1734314509: bad
978-1788399081: good
978-1788399083: bad

Haskell

<lang haskell>import Data.Char (digitToInt, isDigit) import Text.Printf (printf)

pair :: Num a => [a] -> [(a, a)] pair [] = [] pair xs = p (take 2 xs) : pair (drop 2 xs)

 where
   p ps = case ps of
     (x : y : zs) -> (x, y)
     (x : zs) -> (x, 0)

validIsbn13 :: String -> Bool validIsbn13 isbn

 | length (digits isbn) /= 13 = False
 | otherwise = calc isbn `rem` 10 == 0
 where
   digits = map digitToInt . filter isDigit
   calc = sum . map (\(x, y) -> x + y * 3) . pair . digits

main :: IO () main =

 mapM_
   (printf "%s: Valid: %s\n" <*> (show . validIsbn13))
   [ "978-1734314502",
     "978-1734314509",
     "978-1788399081",
     "978-1788399083"
   ]</lang>
Output:
978-1734314502: Valid: True
978-1734314509: Valid: False
978-1788399081: Valid: True
978-1788399083: Valid: False

Or, expressed in terms of cycle:

<lang haskell>import Data.Char (digitToInt, isDigit)

isISBN13 :: String -> Bool isISBN13 =

 (0 ==)
   . flip rem 10
   . sum
   . flip
     (zipWith ((*) . digitToInt) . filter isDigit)
     (cycle [1, 3])

main :: IO () main =

 mapM_
   (print . ((,) <*> isISBN13))
   [ "978-1734314502",
     "978-1734314509",
     "978-1788399081",
     "978-1788399083"
   ]</lang>
Output:
("978-1734314502",True)
("978-1734314509",False)
("978-1788399081",True)
("978-1788399083",False)

IS-BASIC

<lang IS-BASIC>100 PROGRAM "ISBN13.bas" 110 DO 120 PRINT :INPUT PROMPT "ISBN-13 code: ":IS$ 130 IF IS$="" THEN EXIT DO 140 IF ISBN(IS$) THEN 150 PRINT "Ok." 160 ELSE 170 PRINT "CRC error." 180 END IF 190 LOOP 200 DEF TRIM$(S$) 210 LET T$="" 220 FOR I=1 TO LEN(S$) 230 IF S$(I)>CHR$(47) AND S$(I)<CHR$(58) THEN LET T$=T$&S$(I) 240 NEXT 250 LET TRIM$=T$ 260 END DEF 270 DEF ISBN(S$) 280 LET SUM,ISBN=0:LET ISBN$=TRIM$(IS$) 290 IF LEN(ISBN$)<>13 THEN PRINT "Invalid length.":EXIT DEF 300 FOR I=1 TO 11 STEP 2 310 LET SUM=SUM+VAL(ISBN$(I))+VAL(ISBN$(I+1))*3 320 NEXT 330 LET SUM=SUM+VAL(ISBN$(13)) 340 IF MOD(SUM,10)=0 THEN LET ISBN=-1 350 END DEF</lang>

J

<lang j> D =: '0123456789'

  isbn13c      =:  D&([ check@:i. clean)
    check      =:  0 = 10 | lc
      lc       =:  [ +/@:* weight
        weight =:  1 3 $~ #
    clean      =:  ] -. a. -. [</lang>
Output:

<lang j> isbn13c;._1 ' 978-1734314502 978-1734314509 978-1788399081 978-1788399083' 1 0 1 0</lang>

Java

<lang java>public static void main(){

       System.out.println(isISBN13("978-1734314502"));
       System.out.println(isISBN13("978-1734314509"));
       System.out.println(isISBN13("978-1788399081"));
       System.out.println(isISBN13("978-1788399083"));
   }

public static boolean isISBN13(String in){

       int pre = Integer.parseInt(in.substring(0,3));
       if (pre!=978)return false;
       String postStr = in.substring(4);
       if (postStr.length()!=10)return false;
       int post = Integer.parseInt(postStr);
       int sum = 38;
       for(int x = 0; x<10;x+=2)
       sum += (postStr.charAt(x)-48)*3 + ((postStr.charAt(x+1)-48));
       if(sum%10==0) return true;
       return false;
   }

</lang>

Output:
true
false
true
false

jq

Works with: jq

Works with gojq, the Go implementation of jq <lang jq> def isbn_check:

 def digits: tostring | explode | map( select(. >= 48 and . <= 57) | [.] | implode | tonumber);
 def sum(s): reduce s as $x (null; . + $x);
 digits
 | . as $digits
 |      sum(range(0;length;2) | $digits[.]) as $one
 | (3 * sum(range(1;length;2) | $digits[.])) as $two
 | (($one+$two) % 10) == 0;

def testingcodes:

["978-1734314502", "978-1734314509",
 "978-1788399081", "978-1788399083"];

testingcodes[] | "\(.): \(if isbn_check then "good" else "bad" end)" </lang>

Output:
978-1734314502: good
978-1734314509: bad
978-1788399081: good
978-1788399083: bad

Julia

<lang julia>function isbncheck(str)

   return sum(iseven(i) ? 3 * parse(Int, ch) : parse(Int, ch) 
       for (i, ch) in enumerate(replace(str, r"\D" => ""))) % 10 == 0

end

const testingcodes = ["978-1734314502", "978-1734314509",

                     "978-1788399081", "978-1788399083"]

for code in testingcodes

   println(code, ": ", isbncheck(code) ? "good" : "bad")

end

</lang>

Output:
978-1734314502: good
978-1734314509: bad
978-1788399081: good
978-1788399083: bad

Kotlin

<lang Kotlin> fun isValidISBN13(text: String): Boolean {

   val isbn = text.replace(Regex("[- ]"), "")
   return isbn.length == 13 && isbn.map { it - '0' }
       .mapIndexed { index, value -> when (index % 2) { 0 -> value else -> 3 * value } }
       .sum() % 10 == 0

} </lang>

Tested using Spek <lang Kotlin> describe("ISBN Utilities") {

   mapOf(
       "978-1734314502" to true,
       "978-1734314509" to false,
       "978-1788399081" to true,
       "978-1788399083" to false
   ).forEach { (input, expected) ->
       it("returns $expected for $input") {
           println("$input: ${when(isValidISBN13(input)) { 
               true -> "good"
               else -> "bad"
           }}")
           assert(isValidISBN13(input) == expected)
       }
   }

} </lang>

Output:
978-1734314502: good
978-1734314509: bad
978-1788399081: good
978-1788399083: bad

langur

Works with: langur version 0.8.11

In this example, we map to multiple functions (actually 1 no-op). <lang langur>val .isbn13checkdigit = f(var .s) {

   .s = replace(.s, RE/[\- ]/)
   matching(re/^[0-9]{13}$/, .s) and
       fold(f{+}, map [_, f{x 3}], s2n .s) div 10

}

val .tests = h{

   "978-1734314502": true,
   "978-1734314509": false,
   "978-1788399081": true,
   "978-1788399083": false,

}

for .key of .tests {

   val .pass = .isbn13checkdigit(.key)
   write .key, ": ", if(.pass: "good"; "bad")
   writeln if(.pass == .tests[.key]: ""; " (ISBN-13 CHECK DIGIT TEST FAILED)")

}</lang>

Works with: langur version 0.9.0

In this example, we set a for loop value as it progresses. <lang langur>val .isbn13checkdigit = f(var .s) {

   .s = replace(.s, RE/[\- ]/)
   var .alt = true
   matching(re/^[0-9]{13}$/, .s) and
       for[=0] .d in s2n(.s) { _for += if(not= .alt: .d x 3; .d) } div 10

}

val .tests = h{

   "978-1734314502": true,
   "978-1734314509": false,
   "978-1788399081": true,
   "978-1788399083": false,

}

for .key of .tests {

   val .pass = .isbn13checkdigit(.key)
   write .key, ": ", if(.pass: "good"; "bad")
   writeln if(.pass == .tests[.key]: ""; " (ISBN-13 CHECK DIGIT TEST FAILED)")

}</lang>

Output:
978-1734314502: good
978-1734314509: bad
978-1788399081: good
978-1788399083: bad

Lua

Translation of: C

<lang lua>function checkIsbn13(isbn)

   local count = 0
   local sum = 0
   for c in isbn:gmatch"." do
       if c == ' ' or c == '-' then
           -- skip
       elseif c < '0' or '9' < c then
           return false
       else
           local digit = c - '0'
           if (count % 2) > 0 then
               sum = sum + 3 * digit
           else
               sum = sum + digit
           end
           count = count + 1
       end
   end
   if count ~= 13 then
       return false
   end
   return (sum % 10) == 0

end

function test(isbn)

   if checkIsbn13(isbn) then
       print(isbn .. ": good")
   else
       print(isbn .. ": bad")
   end

end

function main()

   test("978-1734314502")
   test("978-1734314509")
   test("978-1788399081")
   test("978-1788399083")

end

main()</lang>

Output:
978-1734314502: good
978-1734314509: bad
978-1788399081: good
978-1788399083: bad


Mathematica / Wolfram Language

<lang Mathematica>ClearAll[ValidISBNQ] ValidISBNQ[iban_String] := Module[{i},

 i = StringReplace[iban, {" " -> "", "-" -> ""}];
 If[StringMatchQ[i, Repeated[DigitCharacter]],
  i = ToExpression /@ Characters[i];
  i2 ;; ;; 2 *= 3;
  Mod[Total[i], 10] == 0
  ,
  False
  ]
 ]

ValidISBNQ["978-1734314502"] ValidISBNQ["978-1734314509"] ValidISBNQ["978-1788399081"] ValidISBNQ["978-1788399083"]</lang>

Output:
True
False
True
False


Modula-2

<lang modula2>MODULE ISBN; FROM InOut IMPORT WriteString, WriteLn; FROM Strings IMPORT Length;

PROCEDURE validISBN(s: ARRAY OF CHAR): BOOLEAN;

   VAR total, i, length: CARDINAL;

BEGIN

   total := 0;
   length := Length(s);
   IF (length # 14) OR (s[3] # '-') THEN
       RETURN FALSE;
   END;
   FOR i := 0 TO length-1 DO
       IF i # 3 THEN
           IF (s[i] < '0') OR (s[i] > '9') THEN
               RETURN FALSE;
           ELSIF (i < 3) AND (i MOD 2 = 1) OR (i > 3) AND (i MOD 2 = 0) THEN
               total := total + 3 * (ORD(s[i]) - ORD('0'));
           ELSE
               total := total + ORD(s[i]) - ORD('0');
           END;
       END;
   END;
   RETURN total MOD 10 = 0;

END validISBN;

PROCEDURE check(s: ARRAY OF CHAR); BEGIN

   WriteString(s);
   WriteString(': ');
   IF validISBN(s) THEN
       WriteString('good');
   ELSE
       WriteString('bad');
   END;
   WriteLn;

END check;

BEGIN

   check('978-1734314502');
   check('978-1734314509');
   check('978-1788399081');
   check('978-1788399083');

END ISBN.</lang>

Output:
978-1734314502: good
978-1734314509: bad
978-1788399081: good
978-1788399083: bad

Nanoquery

Translation of: Go

<lang nanoquery>def checkIsbn13(isbn)

       // remove any hyphens or spaces
       isbn = str(isbn).replace("-","").replace(" ","")
       // check length = 13
       if len(isbn) != 13
               return false
       end
       // check only contains digits and calculate weighted sum
       sum = 0
       for i in range(0, len(isbn) - 1)
               c = isbn[i]
               if (ord(c) < ord("0")) or (ord(c) > ord("9"))
                       return false
               end
               if (i % 2) = 0
                       sum += ord(c) - ord("0")
               else
                       sum += 3 * (ord(c) - ord("0"))
               end
       end
       return (sum % 10) = 0

end

isbns = {"978-1734314502", "978-1734314509", "978-1788399081", "978-1788399083"} for isbn in isbns

       res = "bad"
       if checkIsbn13(isbn)
               res = "good"
       end
       print format("%s: %s\n", isbn, res)

end</lang>

Output:
978-1734314502: good
978-1734314509: bad
978-1788399081: good
978-1788399083: bad

Nim

<lang nim>import strutils, strformat

func is_isbn*(s: string): bool =

 var sum, len: int
 for c in s:
   if is_digit(c):
     sum += (ord(c) - ord('0')) * (if len mod 2 == 0: 1 else: 3)
     len += 1
   elif c != ' ' and c != '-':
     return false
 return (len == 13) and (sum mod 10 == 0)

when is_main_module:

 let isbns = [ "978-1734314502", "978-1734314509",
               "978-1788399081", "978-1788399083" ]
 for isbn in isbns:
   var quality: string = if is_isbn(isbn): "good" else: "bad"
   echo &"{isbn}: {quality}"</lang>
Output:
978-1734314502: good
978-1734314509: bad
978-1788399081: good
978-1788399083: bad

Pascal

Works with: Extended Pascal

<lang pascal>program ISBNChecksum(output);

const codeIndexMaximum = 17; ISBNIndexMinimum = 1; ISBNIndexMaximum = 13; ISBNIndexRange = ISBNIndexMaximum - ISBNIndexMinimum + 1;

type code = string(codeIndexMaximum); codeIndex = 1..codeIndexMaximum value 1; decimalDigit = '0'..'9'; decimalValue = 0..9; ISBNIndex = ISBNIndexMinimum..ISBNIndexMaximum; ISBN = array[ISBNIndex] of decimalDigit;

{ returns the integer value represented by a character } function numericValue(protected c: decimalDigit): decimalValue; begin { in Pascal result variable bears the same name as the function } numericValue := ord(c) - ord('0') end;

{ determines whether an ISBN is technically valid (checksum correct) } function isValidISBN(protected n: ISBN): Boolean; var sum: 0..225 value 0; i: ISBNIndex; begin { NB: in Pascal for-loop-limits are _inclusive_ } for i := ISBNIndexMinimum to ISBNIndexMaximum do begin { alternating scale factor 3^0, 3^1 based on Boolean } sum := sum + numericValue(n[i]) * 3 pow ord(not odd(i)) end;

isValidISBN := sum mod 10 = 0 end;

{ transform '978-0-387-97649-5' into '9780387976495' } function digits(n: code): code; var sourceIndex, destinationIndex: codeIndex; begin for sourceIndex := 1 to length(n) do begin if n[sourceIndex] in ['0'..'9'] then begin n[destinationIndex] := n[sourceIndex]; destinationIndex := destinationIndex + 1 end end;

{ to alter a string’s length you need overwrite it completely } digits := subStr(n, 1, destinationIndex - 1) end;

{ data type coercion } function asISBN(protected n: code): ISBN; var result: ISBN; begin unpack(n[1..length(n)], result, 1); asISBN := result end;

{ tells whether a string value contains a proper ISBN representation } function isValidISBNString(protected hyphenatedForm: code): Boolean; var digitOnlyForm: code; begin digitOnlyForm := digits(hyphenatedForm); { The Extended Pascal `and_then` Boolean operator allows us } { to first check the length before invoking `isValidISBN`. } isValidISBNString := (length(digitOnlyForm) = ISBNIndexRange) and_then isValidISBN(asISBN(digitOnlyForm)) end;

{ === MAIN ============================================================= } begin writeLn(isValidISBNString('978-1734314502')); writeLn(isValidISBNString('978-1734314509')); writeLn(isValidISBNString('978-1788399081')); writeLn(isValidISBNString('978-1788399083')) end.</lang>

Output:
True
False
True
False

Perl

<lang perl>use strict; use warnings; use feature 'say';

sub check_digit {

   my($isbn) = @_; my($sum);
   $sum += (1,3)[$_%2] * (split , join , split /\D/, $isbn)[$_] for 0..11;
   (10 - $sum % 10) % 10;

}

for (<978-1734314502 978-1734314509 978-1788399081 978-1788399083 978-2-74839-908-0 978-2-74839-908-5>) {

   my($isbn,$check) = /(.*)(.)/;
   my $check_d = check_digit($isbn);
   say "$_ : " . ($check == $check_d ? 'Good' : "Bad check-digit $check; should be $check_d")

}</lang>

Output:
978-1734314502 : Good
978-1734314509 : Bad check-digit 9; should be 2
978-1788399081 : Good
978-1788399083 : Bad check-digit 3; should be 1
978-2-74839-908-0 : Good
978-2-74839-908-5 : Bad check-digit 5; should be 0

Phix

<lang Phix>procedure check_isbn13(string isbn)

   integer digits = 0, checksum = 0, w = 1
   for i=1 to length(isbn) do
       integer ch = isbn[i]
       if ch!=' ' and ch!='-' then
           ch -= '0'
           if ch<0 or ch>9 then checksum = 9 exit end if
           checksum += ch*w
           digits += 1
           w = 4-w
       end if
   end for
   checksum = remainder(checksum,10)
   string gb = iff(digits=13 and checksum=0 ? "good" : "bad")
   printf(1,"%s: %s\n",{isbn,gb})

end procedure

constant isbns = {"978-1734314502", "978-1734314509", "978-1788399081", "978-1788399083",

                 "978-2-74839-908-0","978-2-74839-908-5","978 1 86197 876 9"}

for i=1 to length(isbns) do check_isbn13(isbns[i]) end for</lang>

Output:
978-1734314502: good
978-1734314509: bad
978-1788399081: good
978-1788399083: bad
978-2-74839-908-0: good
978-2-74839-908-5: bad
978 1 86197 876 9: good

PicoLisp

<lang PicoLisp>(de isbn13? (S)

  (let L
     (make
        (for N (chop S)
           (and (format N) (link @)) ) )
     (and
        (= 13 (length L))
        (=0 (% (sum * L (circ 1 3)) 10)) ) ) )

(mapc

  '((A)
     (tab
        (-19 1)
        A
        (if (isbn13? A) 'ok 'fail) ) )
  (quote
     "978-1734314502"
     "978-1734314509"
     "978-1-86197-876-9"
     "978-2-74839-908-5"
     "978 1 86197 876 9" ) )</lang>
Output:
978-1734314502     ok
978-1734314509     fail
978-1-86197-876-9  ok
978-2-74839-908-5  fail
978 1 86197 876 9  ok

PL/M

<lang plm>100H:

CHECK$ISBN13: PROCEDURE (PTR) BYTE;

   DECLARE PTR ADDRESS, ISBN BASED PTR BYTE;
   DECLARE (I, F, T) BYTE;
   F = 1;
   T = 0;
   DO I = 0 TO 13;
       IF I = 3 THEN DO;
           /* THIRD CHAR SHOULD BE '-' */
           IF ISBN(I) <> '-' THEN RETURN 0;
       END;
       ELSE DO;
           /* DIGITS MUST BE VALID */
           IF ISBN(I) < '0' OR ISBN(I) > '9' THEN RETURN 0;
           T = T + (ISBN(I) - '0') * F;
           F = 4 - F; /* MULTIPLY BY 1 AND 3 ALTERNATELY */
       END;
   END;
   RETURN (T MOD 10) = 0;

END CHECK$ISBN13;

/* CP/M BDOS CALL */ BDOS: PROCEDURE (FUNC, ARG);

   DECLARE FUNC BYTE, ARG ADDRESS;
   GO TO 5;

END BDOS;

PRINT: PROCEDURE (STR);

   DECLARE STR ADDRESS;
   CALL BDOS(9, STR);

END PRINT;

/* TESTS */ DECLARE TEST (4) ADDRESS; TEST(0) = .'978-1734314502$'; TEST(1) = .'978-1734314509$'; TEST(2) = .'978-1788399081$'; TEST(3) = .'978-1788399083$';

DECLARE I BYTE; DO I = 0 TO LAST(TEST);

   CALL PRINT(TEST(I));
   CALL PRINT(.': $');
   IF CHECK$ISBN13(TEST(I)) THEN
       CALL PRINT(.'GOOD$');
   ELSE
       CALL PRINT(.'BAD$');
   CALL PRINT(.(13,10,'$'));

END;

CALL BDOS(0,0); EOF</lang>

Output:
978-1734314502: GOOD
978-1734314509: BAD
978-1788399081: GOOD
978-1788399083: BAD

PowerShell

<lang PowerShell> function Get-ISBN13 {

   $codes = (
       "978-1734314502",
       "978-1734314509",
       "978-1788399081",
       "978-1788399083"
   )
   foreach ($line in $codes) {
       $sum = $null
       $codeNoDash = $line.Replace("-","")
       for ($i = 0; $i -lt $codeNoDash.length; $i++) {
           if (($i % 2) -eq 1) {
               $sum += [decimal]$codeNoDash[$i] * 3
              
           }else {
               $sum += [decimal]$codeNoDash[$i]
           }
       }
       
       if (($sum % 10) -eq 0) {
           Write-Host "$line Good"
           
       }else {
           Write-Host "$line Bad"
       }
   }

} Get-ISBN13 </lang>

Output:
978-1734314502 Good
978-1734314509 Bad
978-1788399081 Good
978-1788399083 Bad

PureBasic

<lang PureBasic>Macro TestISBN13(X)

 Print(X) 
 If IsISBN13(X) : PrintN(" good") : Else : PrintN(" bad") : EndIf  

EndMacro

Procedure.b IsISBN13(ISBN13.s)

 ISBN13=Left(ISBN13,3)+Mid(ISBN13,5)  
 If IsNAN(Val(ISBN13)) Or Len(ISBN13)<>13 : ProcedureReturn #False : EndIf
 Dim ISBN.s{1}(12) : PokeS(@ISBN(),ISBN13)  
 For i=0 To 11 Step 2 : sum+Val(ISBN(i))+Val(ISBN(i+1))*3 : Next
 sum+Val(ISBN(12))
 ProcedureReturn Bool(sum%10=0)

EndProcedure

If OpenConsole()

 TestISBN13("978-1734314502")
 TestISBN13("978-1734314509")
 TestISBN13("978-1788399081")
 TestISBN13("978-1788399083")
 Input()

EndIf</lang>

Output:
978-1734314502 good
978-1734314509 bad
978-1788399081 good
978-1788399083 bad

Python

<lang python>def is_isbn13(n):

   n = n.replace('-',).replace(' ', )
   if len(n) != 13:
       return False
   product = (sum(int(ch) for ch in n[::2]) 
              + sum(int(ch) * 3 for ch in n[1::2]))
   return product % 10 == 0

if __name__ == '__main__':

   tests = 

978-1734314502 978-1734314509 978-1788399081 978-1788399083.strip().split()

   for t in tests:
       print(f"ISBN13 {t} validates {is_isbn13(t)}")</lang>
Output:
ISBN13 978-1734314502 validates True
ISBN13 978-1734314509 validates False
ISBN13 978-1788399081 validates True
ISBN13 978-1788399083 validates False


Or, expressed in terms of itertools.cycle <lang python>ISBN13 check digit


from itertools import cycle


  1. isISBN13 :: String -> Bool

def isISBN13(s):

   True if s is a valid ISBN13 string
   
   digits = [int(c) for c in s if c.isdigit()]
   return 13 == len(digits) and (
       0 == sum(map(
           lambda f, x: f(x),
           cycle([
               lambda x: x,
               lambda x: 3 * x
           ]),
           digits
       )) % 10
   )


  1. ------------------------- TEST -------------------------
  2. main :: IO ()

def main():

   Test strings for ISBN-13 validity.
   print('\n'.join(
       repr((s, isISBN13(s))) for s
       in ["978-1734314502",
           "978-1734314509",
           "978-1788399081",
           "978-1788399083"
           ]
   ))


  1. MAIN ---

if __name__ == '__main__':

   main()

</lang>

Output:
('978-1734314502', True)
('978-1734314509', False)
('978-1788399081', True)
('978-1788399083', False)

Quackery

<lang quackery>[ char 0 char 9 1+ within ] is digit? ( c --> b )

[ 1 & ] is odd? ( n --> b )

[ [] swap ]'[ swap

 witheach [ 
   dup nested 
   unrot over do
   iff [ dip join ]
   else nip
 ] drop ]                   is filter    ( [ --> [ )

[ 0 swap

 witheach [
   char->n i^ odd?
   iff [ 3 * + ]
   else +
 ] ]                        is checksum  ( $ --> n )

[ filter digit?

 dup size 13 = not
 iff [ drop false ] done
 checksum 10 mod 0 = ]      is isbn      ( $ --> b )

[ dup echo$ say ": " isbn

 iff [ say "Good" ]
 else [ say "Bad" ] cr ]    is isbn-test ( $ -->   )

$ '978-1734314502' isbn-test $ '978-1734314509' isbn-test $ '978-1788399081' isbn-test $ '978-1788399083' isbn-test</lang>

Output:
978-1734314502: Good
978-1734314509: Bad
978-1788399081: Good
978-1788399083: Bad

Racket

<lang racket>#lang racket/base

(define (isbn13-check-digit-valid? s)

 (zero? (modulo (for/sum ((i (in-naturals))
                          (d (regexp-replace* #px"[^[:digit:]]" s "")))
                  (* (- (char->integer d) (char->integer #\0))
                     (if (even? i) 1 3)))
                10)))

(module+ test

 (require rackunit)
 (check-true (isbn13-check-digit-valid? "978-1734314502"))
 (check-false (isbn13-check-digit-valid? "978-1734314509"))
 (check-true (isbn13-check-digit-valid? "978-1788399081"))
 (check-false (isbn13-check-digit-valid? "978-1788399083")))</lang>
Output:

no output indicates tests pass

Raku

(formerly Perl 6)

Works with: Rakudo version 2019.11

Also test a value that has a zero check digit.

<lang perl6>sub check-digit ($isbn) {

    (10 - (sum (|$isbn.comb(/<[0..9]>/)) »*» (1,3)) % 10).substr: *-1

}

{

   my $check = .substr(*-1);
   my $check-digit = check-digit .chop;
   say "$_ : ", $check == $check-digit ??
       'Good' !!
       "Bad check-digit $check; should be $check-digit"

} for words <

   978-1734314502
   978-1734314509
   978-1788399081
   978-1788399083
   978-2-74839-908-0
   978-2-74839-908-5

>;</lang>

Output:
978-1734314502 : Good
978-1734314509 : Bad check-digit 9; should be 2
978-1788399081 : Good
978-1788399083 : Bad check-digit 3; should be 1
978-2-74839-908-0 : Good
978-2-74839-908-5 : Bad check-digit 5; should be 0

REXX

A couple of additional checks were made to verify a correct length,   and also that the ISBN-13 code is all numerics   (with optional minus signs). <lang rexx>/*REXX pgm validates the check digit of an ISBN─13 code (it may have embedded minuses).*/ parse arg $ /*obtain optional arguments from the CL*/ if $= | if $="," then $= '978-1734314502 978-1734314509 978-1788399081 978-1788399083' @ISBN= "ISBN─13 code isn't" /*a literal used when displaying msgs. */

                                                /* [↓]  remove all minuses from X code.*/
 do j=1  for words($);  y= word($,j)            /*obtain an ISBN─13 code from  $  list.*/
 x= space( translate(y, , '-'),  0)             /*remove all minus signs from the code.*/
 L= length(x)                                   /*obtain the length of the ISBN-13 code*/
 if L \== 13                   then do;  say @ISBN  '13 characters: '  x;  exit 13;   end
 if verify(x, 9876543210)\==0  then do;  say @ISBN  'numeric: '        x;  exit 10;   end
 sum= 0
         do k=1  for L;   #= substr(x, k, 1)    /*get a decimal digit from the X code. */
         if \(k//2)  then #= # * 3              /*multiply every other digit by three. */
         sum= sum + #                           /*add the digit (or product) to the SUM*/
         end   /*k*/
 if right(sum, 1)==0  then say '     ISBN-13 code '      x      "    is valid."
                      else say '     ISBN-13 code '      x      " isn't valid."
 end   /*j*/                                    /*stick a fork in it,  we're all done. */</lang>
output   when using the four default inputs:
     ISBN-13 code  9781734314502     is valid.
     ISBN-13 code  9781734314509  isn't valid.
     ISBN-13 code  9781788399081     is valid.
     ISBN-13 code  9781788399083  isn't valid.

Ring

<lang ring> load "stdlib.ring"

isbn = ["978-1734314502","978-1734314509", "978-1788399081", "978-1788399083","978-2-74839-908-0","978-2-74839-908-5","978 1 86197 876 9"]

for n = 1 to len(isbn)

   sum = 0
   isbnStr = isbn[n]
   isbnStr = substr(isbnStr,"-","")
   isbnStr = substr(isbnStr," ","")
   for m = 1 to len(isbnStr)
       if m%2 = 0
          num = 3*number(substr(isbnStr,m,1))
          sum = sum + num
       else
          num = number(substr(isbnStr,m,1))
          sum = sum + num
       ok
   next
   if sum%10 = 0
      see "" + isbn[n] + ": true" + nl
   else
      see "" + isbn[n] + ": bad" + nl
   ok

next </lang> Output:

978-1734314502: true
978-1734314509: bad
978-1788399081: true
978-1788399083: bad
978-2-74839-908-0: true
978-2-74839-908-5: bad
978 1 86197 876 9: true

Ruby

<lang ruby>def validISBN13?(str)

 cleaned = str.delete("^0-9").chars
 return false unless cleaned.size == 13
 cleaned.each_slice(2).sum{|d1, d2| d1.to_i + 3*d2.to_i }.remainder(10) == 0

end

isbns = ["978-1734314502", "978-1734314509", "978-1788399081", "978-1788399083"] isbns.each{|isbn| puts "#{isbn}: #{validISBN13?(isbn)}" }

</lang>

Output:
978-1734314502: true
978-1734314509: false
978-1788399081: true
978-1788399083: false

Rust

<lang rust>fn main() {

   let isbns = ["978-1734314502", "978-1734314509", "978-1788399081", "978-1788399083"];
   isbns.iter().for_each(|isbn| println!("{}: {}", isbn, check_isbn(isbn)));

}

fn check_isbn(isbn: &str) -> bool {

   if isbn.chars().filter(|c| c.is_digit(10)).count() != 13 {
           return false;
   } 
   let checksum = isbn.chars().filter_map(|c| c.to_digit(10))
       .zip([1, 3].iter().cycle())
       .fold(0, |acc, (val, fac)| acc + val * fac);
   checksum % 10 == 0

} </lang>

Output:
978-1734314502: true
978-1734314509: false
978-1788399081: true
978-1788399083: false

Seed7

<lang seed7>$ include "seed7_05.s7i";

const func boolean: isISBN13 (in var string: input) is func

 result
   var boolean: isbn is FALSE;
 local
   var char: c is ' ';
   var integer: digit is 0;
   var integer: i is 1;
   var integer: sum is 0;
 begin
   input := replace(input, " ", "");
   input := replace(input, "-", "");
   if length(input) = 13 then
     for c range input do
       digit := ord(c) - 48;
       if not odd(i) then
         digit *:= 3;
       end if;
       sum +:= digit;
       incr(i);
     end for;
     isbn := sum rem 10 = 0;
   end if;
 end func;

const proc: main is func

 local
   var string: str is "";
 begin
   for str range [] ("978-1734314502", "978-1734314509", "978-1788399081", "978-1788399083") do
     writeln(str <& ": " <& isISBN13(str));
   end for;
 end func;</lang>
Output:
978-1734314502: TRUE
978-1734314509: FALSE
978-1788399081: TRUE
978-1788399083: FALSE

Standard ML

<lang sml>local

 fun check (c, p as (m, n)) =
   if Char.isDigit c then
     (not m, (if m then 3 * (ord c - 48) else ord c - 48) + n)
   else
     p

in

 fun checkISBN s =
   Int.rem (#2 (CharVector.foldl check (false, 0) s), 10) = 0

end

val test = ["978-1734314502", "978-1734314509", "978-1788399081", "978-1788399083"] val () = (print

         o concat
         o map (fn s => s ^ (if checkISBN s then ": good\n" else ": bad\n"))) test</lang>
Output:
978-1734314502: good
978-1734314509: bad
978-1788399081: good
978-1788399083: bad

Swift

<lang swift>func checkISBN(isbn: String) -> Bool {

 guard !isbn.isEmpty else {
   return false
 }
 let sum = isbn
   .compactMap({ $0.wholeNumberValue })
   .enumerated()
   .map({ $0.offset & 1 == 1 ? 3 * $0.element : $0.element })
   .reduce(0, +)
 return sum % 10 == 0

}

let cases = [

 "978-1734314502",
 "978-1734314509",
 "978-1788399081",
 "978-1788399083"

]

for isbn in cases {

 print("\(isbn) => \(checkISBN(isbn: isbn) ? "good" : "bad")")

}</lang>


Output:
978-1734314502 => good
978-1734314509 => bad
978-1788399081 => good
978-1788399083 => bad

Tcl

<lang tcl> proc validISBN13 code {

  regsub -all {\D} $code "" code ;# remove non-digits
  if {[string length $code] == 13} {
     set sum 0
     set fac 1
     foreach digit [split $code ""] {
        set sum [expr {$sum + $digit * $fac}]
        set fac [expr {$fac == 1? 3: 1}]
     }
     if {$sum % 10 == 0} {return true}
  }
  return false

} foreach test {

  978-1734314502
  978-1734314509
  978-1788399081
  978-1788399083

} {puts $test:[validISBN13 $test]} </lang>

Output:
978-1734314502:true
978-1734314509:false
978-1788399081:true
978-1788399083:false

Simpler variant, using two loop vars and constant factors; same output: <lang tcl> proc validISBN13 code {

  regsub -all {\D} $code "" code ;# remove non-digits
  if {[string length $code] == 13} {
     set sum 0
     foreach {d1 d2} [split $code ""] {
        if {$d2 eq ""} {set d2 0} ;# last round
        set sum [expr {$sum + $d1 + $d2 * 3}]
     }
     if {$sum % 10 == 0} {return true}
  }
  return false

} </lang>

uBasic/4tH

Works with: version 3.64.0

<lang>a := "978-1734314502" Print Show(a), Show (Iif (Func(_IsISBN (a)), "good", "bad"))

a := "978-1734314509" Print Show(a), Show (Iif (Func(_IsISBN (a)), "good", "bad"))

a := "978-1788399081" Print Show(a), Show (Iif (Func(_IsISBN (a)), "good", "bad"))

a := "978-1788399083" Print Show(a), Show (Iif (Func(_IsISBN (a)), "good", "bad"))

End

_IsISBN

 Param (1)
 Local (4)
 c@ = 0 : e@ = 1                      ' set sum and multiplier
 For b@ = Len (a@) - 1 To 0 Step -1   ' scan string in reverse
   d@ = Peek(a@, b@) - Ord ("0")      ' get character
   If ((d@ < 0) + (d@ > 9)) = 0 Then c@ = c@ + (d@ * e@)
   e@ = (e@ * 3) % 8                  ' evaluate character, change multiplier
 Next

Return ((c@ % 10) = 0) ' modulus 10 must be zero</lang>

Output:
978-1734314502  good
978-1734314509  bad
978-1788399081  good
978-1788399083  bad

0 OK, 0:339

UNIX Shell

Works with: Bourne Again Shell

<lang sh>check_isbn13 () {

   local i n t
   n=${1//[^0-9]/}
   t=0
   for ((i=0; i<${#n}; ++i )); do
     (( t += ${n:i:1}*(1 + ((i&1)<<1)) ))
   done
   (( 0 == t % 10 ))

}

for isbn in 978-1734314502 978-1734314509 978-1788399081 978-1788399083; do

 printf '%s: ' "$isbn"
 if check_isbn13 "$isbn"; then
   printf '%s\n' OK
 else
   printf '%s\n' 'NOT OK'
 fi

done</lang>

Output:
978-1734314502: OK
978-1734314509: NOT OK
978-1788399081: OK
978-1788399083: NOT OK

Visual Basic .NET

Translation of: C#

<lang vbnet>Module Module1

   Function CheckISBN13(code As String) As Boolean
       code = code.Replace("-", "").Replace(" ", "")
       If code.Length <> 13 Then
           Return False
       End If
       Dim sum = 0
       For Each i_d In code.Select(Function(digit, index) (index, digit))
           Dim index = i_d.index
           Dim digit = i_d.digit
           If Char.IsDigit(digit) Then
               sum += (Asc(digit) - Asc("0")) * If(index Mod 2 = 0, 1, 3)
           Else
               Return False
           End If
       Next
       Return sum Mod 10 = 0
   End Function
   Sub Main()
       Console.WriteLine(CheckISBN13("978-1734314502"))
       Console.WriteLine(CheckISBN13("978-1734314509"))
       Console.WriteLine(CheckISBN13("978-1788399081"))
       Console.WriteLine(CheckISBN13("978-1788399083"))
   End Sub

End Module</lang>

Output:
True
False
True
False

Wren

<lang ecmascript>var isbn13 = Fn.new { |s|

   var cps = s.codePoints
   var digits = []
   // extract digits
   for (i in 0...cps.count) {
       var c = cps[i]
       if (c >= 48 && c <= 57) digits.add(c)
   }
   // do calcs
   var sum = 0
   for (i in 0...digits.count) {
       var d = digits[i] - 48
       sum = sum + ((i%2 == 0) ? d : 3 * d)
   }
   return sum % 10 == 0

}

var tests = ["978-1734314502", "978-1734314509", "978-1788399081", "978-1788399083"] for (test in tests) {

   System.print("%(test) -> %(isbn13.call(test) ? "good" : "bad")")

}</lang>

Output:
978-1734314502 -> good
978-1734314509 -> bad
978-1788399081 -> good
978-1788399083 -> bad

XPL0

<lang XPL0>include xpllib; \contains StrLen function

proc ISBN13(Str); \Show if International Standard Book Number is good char Str; int Sum, Cnt, Dig, I; [Sum:= 0; Cnt:= 0; for I:= 0 to StrLen(Str)-1 do

   [Dig:= Str(I) - ^0;
   if Dig>=0 & Dig<=9 then
       [Sum:= Sum + Dig;
        Cnt:= Cnt + 1;
        if (Cnt&1) = 0 then
               Sum:= Sum + Dig + Dig;
       ];
   ];

Text(0, Str); Text(0, if rem(Sum/10)=0 & Cnt=13 then ": good" else ": bad"); CrLf(0); ];

[ISBN13("978-1734314502");

ISBN13("978-1734314509");
ISBN13("978-1788399081");
ISBN13("978-1788399083");
ISBN13("978-1-59327-220-3");
ISBN13("978-178839918");

]</lang>

Output:
978-1734314502: good
978-1734314509: bad
978-1788399081: good
978-1788399083: bad
978-1-59327-220-3: good
978-178839918: bad

zkl

<lang zkl>fcn ISBN13_check(isbn){ // "978-1734314502", throws on invalid digits

  var [const] one3=("13"*6 + 1).split("").apply("toInt"); // examine 13 digits
  // one3=("13"*6) if you want to calculate what the check digit should be
  one3.zipWith('*,isbn - " -").sum(0) % 10 == 0

}</lang> <lang zkl>isbns:=

  1. <<<"
   978-1734314502
   978-1734314509
   978-1788399081
   978-1788399083
   978-2-74839-908-0
   978-2-74839-908-5".split("\n");
  1. <<<

foreach isbn in (isbns)

  { println(isbn.strip(),"  ",ISBN13_check(isbn) and " Good" or " Bad") }</lang>
Output:
978-1734314502   Good
978-1734314509   Bad
978-1788399081   Good
978-1788399083   Bad
978-2-74839-908-0   Good
978-2-74839-908-5   Bad