ローマ数字の概要と、アラビア数字との変換アルゴリズム紹介

先日、たまたま書店に行ったときのことだ。

漫画「黒執事」が目に入った。

で、今何巻まで出てるんだろうか?と思い、ちょっと探してみた。

見つかったのが、以下のものだ。2020/2/20時点での最新刊。

タイトルの括弧に答えが入ってしまっているが、この表紙の中央下部に注目して欲しい。分かりづらかったら実際にアクセスして頂ければはっきりと読める。

「\(XXIX\)」と書かれている。

これは、ローマ数字というものだ。

さて、これだけを見て、あなたは何巻か分かるだろうか

逆に、〇巻が欲しいとなったら、どんなものがここに書かれている巻を買えばいいのだろうか

今回は、これが読み書きできるように解説していこうと思う。

スポンサーリンク

みんな読み書きできる?

Twitterでアンケートを取ってみた。

ずばり、「あなたはローマ数字が書けるor読めますか?」というものだ。

その結果は、以下の通り。

…8票も入って正直嬉しかった。ご投票頂いた方々、改めて感謝する。

さて、全部書ける、と回答頂いた方が二人もいらっしゃったのが驚きだ。

逆に、存在しか知らない、あるいは存在自体知らない方もいらっしゃるようだ。

ちなみに、私は最初、何も見ないと上から二番目、ちょっとくらいなら読めるに該当していた。

ローマ数字の解説の前に

そもそも、私たちが普段使っている数字「1, 2, 3, …」という表記、何数字と呼ぶかご存じだろうか

これは、アラビア数字というものだ。

アラビア数字の書き方では、まず「0」から「9」までの10種類の記号を使う。

で、数を数えるとき、この記号を、順番に沿って一つずつ増やしていく

一番下(1の位)が9からさらに増えるとき、桁上がりが発生して一つ記号を増やし、上に1を足す。その時、下の位は0に戻る。

こんな考え方を、ほぼ無意識レベルで使用しているのだ。

で、このように10種類の記号を使って、桁上がりをしながらカウントする記法を「十進法」と言う。その名の通り、十になったら一つ上に進むのだ。

…何をいまさら、と思うかもしれない。が、安心して欲しい。書いている私も同じことを思っている。じゃあなんで書いたかって?後でローマ数字と比較するためだ。

で、このアラビア数字の利点は何だろうか

まあ色々あるだろうが、一番は「いくら数字が大きくなっても、新しい定義が必要にならない」ことだろう。

具体的な数字であれば、どんなものでも、元々定義されている0~9を使用することで、同じ考え方を使って書くことができる。

ローマ数字

ローマ数字は、上のような記法とは異なる。

まず、各桁の1、5を表す記号が用意されている。具体的なものは以下に挙げよう。

アラビア数字 ローマ数字
1 \(I\)
5 \(V\)
10 \(X\)
50 \(L\)
100 \(C\)
500 \(D\)
1000 \(M\)

ここまでだ。これを、記法に従って組み合わせていく。が、そのまま記法を書くより、アラビア数字を変換していった方がわかりやすいだろう。

アラビア数字→ローマ数字の変換方法

さあ、早速変換していこう。

ここでの入力は、1から3999までのアラビア数字。「あれ、4000以上は?」と思われた方へ。一般的な記法では、ローマ数字で4000以上を表すことができない

で、入力されたものを変換していく。

まず、入力されたアラビア数字の一番大きい位から見ていこう。

その位の数字が1~3の場合

そこの数字が、まずは1から3の場合。このときは簡単で、ローマ数字におけるその位の1を表す記号を、その数だけ続ける

例えば、352という数字を入れたとしよう。このとき、一番大きい位のアラビア数字は3だ。

つまり、ローマ数字の100を表す\(C\)を3回書く。先頭は、「\(CCC\)」となるのだ。

ちなみに、上に書いた通りローマ数字は1000を表す\(M\)までしか用意されていない。つまり、1000の位は大きくても3までしか書き表せないのだ。100の位以下は9まで表せるので、最高でも3999となる。

その位の数が4、9の場合

次に、そこの数字が4もしくは9の場合。このときは、一つ上のものから1引く、という考え方をする。

書き表すときは、その一つ上…4なら、5を表す記号、9なら、一つ上の桁の1を表す記号の左側に、引く1を書く

例えば、458という数字の場合。一番上の4は、まず100の位の5を用意する。\(D\)だ。

で、その左側に、100を表す\(C\)を一つ。つまり、先頭は「\(CD\)」となる。

もう一つ、999という数字も見てみよう。今度は一番上が9だ。つまり、一つ上の1000を表す\(M\)から、100を表す\(C\)を引く。つまり、先頭は「\(CM\)」となる。

その位の数が5~8の場合

これは簡単だ。5に数字を足すイメージ。

まず、先頭にその桁の5を表す記号を書く。そして、足すものの数だけその桁の1を表すものを続けるのだ。

5の場合、足すものは0なので、一個も書かない。

例えば、532という数字。100の位に注目しよう。

5なので、500を表す\(D\)を書く。で、今回は足す必要がないので、それでOKだ。

次に、745という数字。こっちは、7なので5+2と分解できる。

そこで、まずは500を表す\(D\)を書き、そこに100を表す\(C\)を二回書く。つまり、\(DCC\)となる。

その位の数が0の場合

これは非常に簡単だ。何も書かない

さあ、ここまでで1つの位についてローマ数字に直せるようになった。

そうしたら、あとはこれを上の桁から順番に、全部の桁にやってあげるだけだ。

そして、大きい位から左に詰めて書く。これで、ローマ数字の完成だ。

アラビア数字→ローマ数字の具体例

というわけで、具体例を使ってアラビア数字を実際にローマ数字に直してみよう。

例えば、2907という数字を見てみる。上のパターン全てが入っている。

まず、1000の位。2が入っている。そのため、1000を表す\(M\)を2回、\(MM\)だ。

次に、100の位。9なので、1000-100としたいので、1000を表す\(M\)の左に、100を表す\(C\)を、つまり\(CM\)と書く。

そして、10の位。ここは0なので何も書かない。

最後に、1の位。7なので、5+2とする。つまり、5を表す\(V\)の右に、2を表す\(II\)を書いてあげる。つまり、\(VII\)だ。

それが終わったら、ここまでできたものを、1000の位から順に左詰めで書いていく。

最終的にできるローマ数字は、「\(MMCMVII\)」となる。

ローマ数字の読み方

本題はここからだ。これが分からないと、黒執事の巻数がわからない。

まず、先頭から順番に見ていこう。上の例「\(MMCMVII\)」で見ていく。

先頭を見ると、\(M\)が連続している。\(M\)は1000で、連続している分だけ足していく。そうするとここで2000だ。

で、次。\(CM\)とある。\(C\)は100、つまり数字が増えている。

これは、大きい方から小さい方を引けばいい。なので、1000-100で900になる。ここまでで、2900だ。

そして、\(V\)…5が来ているので、10の位は何もない。0だ。

で、この1の位。今度は\(VII\)と、同じ桁の5→1というふうに並んでいる。これは、5に後ろの1を、その1の数だけ足す。これで7だ。結果、2907となる。

おおざっぱに書いたが、大体こんな感じだ。

ローマ数字→アラビア数字の変換方法

では、厳密に変換する方法を見ていこう。さらに、それが本当にローマ数字なのかも同時に見ていく。

入力は、もちろんローマ数字。ただし、条件として、上に書いた定義表のいずれかの文字を組み合わせた文字列だとしよう。そうでないとそもそもローマ数字になり得ない

まず、方針について。どこが区切りかは見た目ではわかりづらいので、先頭から見て、桁ごとに処理していく方針にしよう。

これは、最初は先頭から見ていって、処理の中で次の桁の先頭がどこか判断することで、次の桁はそこから処理できる。

というわけで、注目している桁の先頭1文字を読んでみる。これを\(A\)としておこう。

で、次の文字を読む。これを\(B\)としておこう。つまり、\(AB\)と並んでいることになる。

最初に、簡単なものから。\(B\)が存在しない場合だ。このときは、単純に\(A\)が表す数がその桁になる。さらに、その時点で続きはないので処理は終了だ。

次に、\(B\)が存在する場合。ここで、\(A\)に関する条件分岐を入れよう。

\(A\)が各桁の1を表す場合

このとき、\(B\)は以下のいずれかになる。

  • \(B\)が、\(A\)の一つ上の桁の1を表す場合
  • \(B\)が、\(A\)と同じ桁の5を表す場合
  • \(B\)が、\(A\)と同じ場合
  • \(B\)が、\(A\)より小さい場合

一つ目、二つ目、四つ目の場合は、これで一つの桁が完成する。

一つ目は、\(A\)の桁が9である。次は、\(B\)の後ろからまた読み始める。

二つ目は、\(A\)の桁が4である。次は、\(B\)の後ろからまた読み始める。

四つ目は、\(A\)の桁が1である。次は、\(B\)からまた読み始める

三つ目は、更に3文字目も見てみよう。それが異なるなら、その位が2となり、3文字目から次の桁が始まる。同じなら、その位が3で、4文字目から次の桁だ。

で、これに当てはまらない場合。具体的には、\(B\)が、\(A\)の一つ上の桁の5以上の場合だ。具体例でいうと、\(XM\)の場合など。これは、ローマ数字ではない

\(A\)が各桁の5を表す場合

このとき、\(B\)のパターンは以下のいずれか。

  • \(B\)が、\(A\)と同じ桁の1を表す場合
  • \(B\)の桁が、\(A\)の桁より小さい場合

一つ目は、6~9になる。\(B\)がいくつ続いているかカウントして、その数だけ5に足したものをその桁の数とすればいい。次の桁は、その後ろからだ。

二つ目は、この時点でその桁が5で確定する次の桁はまた\(B\)から見始めればいい。

それ以外のパターン、具体的には\(B\)が\(A\)以上(同じ場合を含む)のときが、ローマ数字にならない

さあ、一桁の判断がついた。というわけで、残りは桁ごとにこれらを繰り返せばいい

そうすることで、ローマ数字がアラビア数字に変換できる、あるいは変換できない場合も分かる。

もっと分かりやすくならないの?

という突っ込みが予想できる。大丈夫、私も書いていて「やたら難しくなったな…」とか思ってるので安心して欲しい。いや、安心できないか。

というわけで、まずは色々試せるようにサンプルページを用意した。

アクセスすると、殺風景なページが出てくると思う。上がローマ数字とアラビア数字の対応、下が変換器だ。

上は、ローマ数字が、どのアラビア数字に対応しているかを表している。ちょっとオマケで、ローマ数字に使う記号を変えれるようにしておいた。何に使うかだが、小文字で表現する場合もあるだろう。それに使ってほしい。

また、全然違う文字を対応させることで、暗号みたいなものも作れる。これで、パスワード生成なんかしても面白いかもしれない。覚えづらそうだけど。

ここでは二つ条件があり、まずは「同じものは2か所に書けない」。二つ同じものがあると、それがどっちを表すか分からなくなってしまう。

次に、「二文字以上にしない」。…すまない、プログラムを1文字用で組んでしまったので、2文字以上を一つの記号として扱うことができない。実際は、はっきりと他のものと区別がつくのであれば、二文字以上でも可だ。

下は、変換器。上で決めたものに従って、ローマ数字からアラビア数字と、アラビア数字からローマ数字に変換できるようにしてみた。

片方に変換元を入れて、ボタンを押すともう片方に答えが出る仕組みだ。ただ、変換できない場合はその旨が表示される。

サンプルページの紹介はこんなものだろう。では、もう一つ。

アラビア数字→ローマ数字は、まだわかりやすいと思う。でも、逆は非常に長い。

というわけで、プログラム風に書いてみることにしよう。その方が、直感的にもわかりやすくなる。

以下が、疑似コードだ。

input:入力されたローマ数字。先頭を0文字目とする。
res:結果のアラビア数字。最初は0。

if(inputに対応がない文字が入っている){
    エラー。処理終了。
}

i番目の文字に注目する。iの初期値は0。
A(i)は、i番目のローマ数字を表すとする。
while(iがinputの文字数より小さい){ ※この処理一回で、1桁求める
    if(i == inputの文字数-1){ ※最後の文字を見ていた場合
        resに、A(i)に対応するアラビア数字を加算し、処理終了。
    }
    if(A(i)がI, X, C, Mのいずれかである){
        if(A(i):A(i+1)の対応がI:X, X:C, C:Mのいずれかの場合){
            この桁は9確定。resの対応する桁に9を代入。
            iを二つ後ろにずらし、ループ先頭に戻る。
        }else if(A(i):A(i+1)の対応がI:V, X:L, C:Dのいずれかの場合){
            この桁は4確定。resの対応する桁に4を代入。
            iを二つ後ろにずらし、ループ先頭に戻る。
        }else if(A(i)と同じ文字が続いている場合){
            続いている数をカウントし、resの対応する桁に代入。
            iを続いている数の後ろにずらし、ループ先頭に戻る。
        }else if(A(i+1)が、A(i)より小さい場合){
            この桁は1確定。resの対応する桁に1を代入。
            iを一つ後ろにずらし、ループ先頭に戻る。
        }else{ ※A(i+1)が、A(i)より上の桁を表す場合
            ローマ数字の決まりに反している。エラーなので、処理終了。
        }
    }else{ ※inputのi文字目がV, L, Dを表す
        if(A(i+1)が、A(i)より小さい桁を表す場合){
            この桁は5確定。resの対応する桁に5を代入。
            iを一つ後ろにずらし、ループ先頭に戻る。
        }else if((A+i)が、A(i)の桁の1を表す場合){
            A(i+1)と同じものがいくつ続くかカウントする。
            このカウント+5を、resの対応する桁に代入。
            iを続いている数の後ろにずらし、ループ先頭に戻る。
        }else{ ※A(i+1)が、A(i)より上の桁を表す場合
            ローマ数字の決まりに反している。エラーなので、処理終了。
        }
    }
}
resが決定。

これはこれでわかりづらい気がする。フローチャートでも書けばよかったかもしれない。

まあ、プログラムで組んでみたい人は参考にしてみて欲しい。

ローマ数字のメリット・デメリット

…と書いたが、正直メリットはそんなにないかもしれない

メリットとしては、まずカッコいい。漫画の巻数なんかはこれだろう。特に中世を舞台にしているのであれば、見事にマッチする。

次に、サンプルページ紹介の部分でちらっと書いたが、わかりづらいことを逆手にとって、パスワードなんかに使える。特に、対応する文字を変えれば、自分にしかわからない暗号の完成だ。ただ、法則を見破られたら厄介なので、少なくとも各対応は2文字以上にした方が良さそう。

デメリットは…普段使うには致命的すぎる分かりづらさ。慣れてないと「えっと、まずはMだから…」みたいに考えなければならない。

これではなかなか使える人が少ないのも頷けてしまう。

また、ローマ数字は二・五進数という考え方を使っている。5と10で、つまり2か所で桁が変わるイメージだ。二種類の進数を同時に扱っているので、これも分かりづらさを助長している。

そして、これも厄介なのだが、桁が増えると新しい文字の定義が必要になる。4000以上を書くためには、5000を新たに定義しなければいけない。

これでは、書ける数字がどう頑張っても有限だ。

おわりに

いかがだっただろうか。

え、今回何で書いたかだって?思いついちまったからだよチクショウ

…まあ、プログラムは組んでて楽しかったので良しとする。

今回書いたプログラムはJavaScriptで書いている。そのJavaScript解説もやっているのでよかったら。

この記事投稿段階での最新記事である繰り返し処理まででも、処理の部分はなんとなく理解できるのではないかと思う。また、次回の内容で表示と処理を絡めた部分も大体はわかるようになる。

よかったら、照らし合わせて何をやっているか見てみて欲しい。

コメント

  1. […] 2020/2/21 追記昨日公開した。ローマ数字についての解説と、変換アルゴリズムの紹介だ。今回の内容までで、処理部分は実装できる。また、次回の内容で値の受け渡しも可能になるので、よかったら是非挑戦してみて欲しい。 […]

タイトルとURLをコピーしました