Delphi で ISBN のチェックディジットを計算してみる

# はじめに
書籍には **ISBN (国際標準図書番号)** というコードが付いています。

例えば古い書籍を調べていたとします。この書籍の第 2 版の ISBN-10 コードが Google 先生で調べてみても判りませんでした。

| 版 | ISBN-10 | 出版年 |
|:---:|:---|:---:|
|第 1 版| 3519023385  | 1977 |
|第 2 版| ?????????? | 1981 |
|第 3 版| 3519223384  | 1984 |
|第 4 版| 3519323389 | 1986 |

...ですが、この ISBN コード、よく見ると前から 5 文字目が連番になっているようです。恐らく第 2 版は **351912338?** だと思われるのですが、最後の一文字 (チェックディジット) が判らないため、10 桁の ISBN コードを得ることができません。

実際には前 9 桁が判れば Google 先生で引っ掛かる事が多いのですが、折角なので Delphi で ISBN のチェックディジットを求めてみたいと思います。

**See also:**

 - [ISBN (Wikipedia)](https://ja.wikipedia.org/wiki/ISBN)

# コード (ISBN-10)

チェックディジットの求め方は Wikipedia にある通りですが、英語版 Wikipedia の説明の方が親切だと思います。

**See also:**

 - [ISBN (Wikipedia:en)](https://en.wikipedia.org/wiki/International_Standard_Book_Number)


## Delphi の場合

使用する Delphi は **10.4 Sydney** です。インライン変数宣言を使っているため、**10.3 Rio** よりも前の Delphi をお使いの場合にはコードの修正が必要となります。

数字以外の文字入力を無視するようにしたため、ソースコードが少し長くなっていますが、`3-519-02338-5` のようにハイフン込みで入力しても、チェックディジットを正しく計算してくれます。

```pascal:isbn10.dpr
program ISBN10;
{$APPTYPE CONSOLE}
uses
  System.SysUtils;
const
  STARTNUM = Ord('0');
begin
  Write('Input ISBN-10: ');
  var s := StringOfChar('0', 9);
  var idx := 1;
  while not Eoln do
    begin
      var c: Char;
      Read(c);
      if CharInSet(c, ['0'..'9']) then
        begin
          s[idx] := c;
          idx := idx + 1;
          if idx = 10 then
            Break;
        end;
    end;
  Writeln;
  var v1 := 0;
  for var i in [1..9] do
    v1 := v1 + (Ord(s[i]) - STARTNUM) * (11 - i);
  var v2 := (11 - (v1 mod 11)) mod 11;
  var cd: Char;
  if v2 = 10 then
    cd := 'X'
  else
    cd := Chr(STARTNUM + v2);
  Writeln('The check digit of ', s,  ' is ', cd, '.');
end.
```

**See also:**

 - [Delphi (Embarcadero)](https://www.embarcadero.com/jp/products/delphi)
 - [Delphi Community Edition (Embarcadero)](https://www.embarcadero.com/jp/products/delphi/starter)



## 標準 Pascal の場合

折角なので**標準 Pascal** でも書いてみました。Delphi のコードは標準 Pascal への移植を意識したものになっているため、大きな違いはありません。検証に使ったのは **Pascal-P5** です。

```pascal:isbn10.pas
program ISBN10(Input, Output);
label
  1;
var
  i, idx,  sn, v1, v2: Integer;
  s: packed array [1..9] of Char;
  c, cd: Char;
begin
  sn := Ord('0');
  Write('Input ISBN-10: ');
  s := '000000000';
  idx := 1;
  while not Eoln do
    begin
      Read(c);
      if c in ['0'..'9'] then
        begin
          s[idx] := c;
          idx := idx + 1;
          if idx = 10 then
            goto 1;
        end;
    end;
  Writeln;  
1:    
  v1 := 0;
  for i:=1 to 9  do
    v1 := v1 + (Ord(s[i]) - sn) * (11 - i);
  v2 := (11 - (v1 mod 11)) mod 11;
  if v2 = 10 then
    cd := 'X'
  else
    cd := Chr(sn + v2);
  Writeln('The check digit of ', s, ' is ', cd, '.');
end.
```

**See also:**

 - [割と簡単に '標準 Pascal' を試してみたい (Qiita)](https://qiita.com/ht_deko/items/41e95154e8da2f901698)
 - [Pascal-P シリーズについて (Qiita)](https://qiita.com/ht_deko/items/adab81f28295a124e9eb)

# コード (ISBN-13)

ISBN-13 のチェックディジットも求めてみましょう。こちらのほうがアルゴリズム的にはスッキリ書けます。

## Delphi の場合

ISBN-10 のものと比べて、コードが少し短くなっています。

```pascal:isbn13.dpr
program ISBN13;
{$APPTYPE CONSOLE}
uses
  System.SysUtils;
const
  STARTNUM = Ord('0');
  WEIGHT: array [Boolean] of Integer = (3, 1);
begin
  Write('Input ISBN-13: ');
  var s := StringOfChar('0', 12);
  var idx := 1;
  while not Eoln do
    begin
      var c: Char;
      Read(c);
      if CharInSet(c, ['0'..'9']) then
        begin
          s[idx] := c;
          idx := idx + 1;
          if idx = 13 then
            Break;
        end;
    end;
  Writeln;
  var v1 := 0;
  for var i in [1..12] do
    v1 := v1 + (Ord(s[i]) - STARTNUM) * WEIGHT[Odd(i)];
  var v2 := 10 - (v1 mod 10);
  var cd := Chr(STARTNUM + v2);
  Writeln('The check digit of ', s,  ' is ', cd, '.');
end.
```


## 標準 Pascal の場合

こちらもコードが少し短くなっています。

```pascal:isbn13.pas
program ISBN13(Input, Output);
label
  1;
var
  i, idx,  sn, v1, v2: Integer;
  s: packed array [1..12] of Char;
  c, cd: Char;
  w: array [Boolean] of Integer;
begin
  sn := Ord('0');
  w[False] := 3; w[True] := 1;
  Write('Input ISBN-13: ');
  s := '000000000000';
  idx := 1;
  while not Eoln do
    begin
      Read(c);
      if c in ['0'..'9'] then
        begin
          s[idx] := c;
          idx := idx + 1;
          if idx = 13 then
            goto 1;
        end;
    end;
  Writeln;  
1:    
  v1 := 0;
  for i:=1 to 12  do
    v1 := v1 + (Ord(s[i]) - sn) * w[Odd(i)];
  v2 := 10 - (v1 mod 10);
  cd := Chr(sn + v2);
  Writeln('The check digit of ', s, ' is ', cd, '.');
end.
```

# ISBN-10 と ISBN-13 の相互変換

ISBN-10 と ISBN-13 は変換可能です。例として次の ISBN コードを変換してみます。

| ISBN | コード |
|:-:|:-:|
| **ISBN-10**  | 3519023385 |
| **ISBN-13**  | 9783519023388 |

## ISBN-10 から ISBN-13 へ

ISBN-13 のプログラムを使います。固定の **接頭記号** と ISBN-10 の先頭 9 文字を入力します (10 文字のままでも構いません)。

| 接頭記号 | コード | CD |
|:-:|:-:|:-:|
| <font color="Crimson">978</font> [^1] | <font color="Crimson">351902338</font> | 5 |

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/fb358d99-49ef-adc6-85a5-69c22613b071.png)
ISBN-13 コード `9783519023388` が得られました。

## ISBN-13 から ISBN-10 へ

ISBN-10 のプログラムを使います。接頭記号の先頭 3 文字と最後のチェックディジットを抜いた 9 文字を入力します (先頭 3 文字を抜いた 10 文字でも構いません)。 

| 接頭記号 | コード | CD |
|:-:|:-:|:-:|
| 978 [^1] | <font color="Crimson">351902338</font> | 8 |

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/f181d5ab-a4e6-8562-8f72-f563e2d0d42b.png)
ISBN-10 コード `3519023385` が得られました。

# おわりに

冒頭の **351912338?** のチェックディジットは `X` です。ISBN-10 は `351912338X` だったのですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/4ade7623-dbfa-7a72-11e5-df0b2cd6811d.png)
Google 先生にこのコードを尋ねても大した情報は得られないという...ちなみに書籍名は**『Compilerbau: Eine Einführung (第 2 版)』**です。

今年は Delphi 25 周年、Pascal 50 周年という事でイロイロな Delphi / Pascal 関連書籍を読んだり調べたりしました。

**See also:**

 - [Pascal / Delphi 関連の書籍を読んでみる (まとめ) (Qiita)](https://qiita.com/ht_deko/items/34bbcdea27ce224079e0)

[^1]: 一部の国では接頭記号が `979` であるため、ISBN-10 から ISBN-13 への変換は完全ではありません。