ISBN13 check digit
- Task
Validate the check digit of an ISBN-13 code. Multiply every other digit by 3. Add the digits together. Take the remainder of 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
See https://isbn-information.com/the-13-digit-isbn.html for details on the method of validation.
AWK
<lang AWK>
- 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
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
Factor
<lang factor>USING: combinators.short-circuit formatting kernel math math.parser math.vectors qw sequences sequences.extras sets unicode ;
- (isbn13?) ( str -- ? )
string>digits [ <evens> sum ] [ <odds> 3 v*n sum + ] bi 10 mod zero? ;
- 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
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
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
langur
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 .d x 3], map(f .c-'0', s2cp .s))) rem 10 == 0
}
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>
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 map(f .c-'0', s2cp .s) { _for += if(.alt=not .alt: .d x 3; .d) } rem 10 == 0
}
val .tests = h{
"978-1734314502": true, "978-1734314509": false, "978-1788399081": true, "978-1788399083": false,
}
for .key in sort(keys .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
Nanoquery
<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
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
Perl 6
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
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
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
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.
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
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:=
- <<<"
978-1734314502 978-1734314509 978-1788399081 978-1788399083 978-2-74839-908-0 978-2-74839-908-5".split("\n");
- <<<
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