Location : Home > Languages > Perl > Package
Title : Math::Roman - Arbitrary sized Roman numbers and conversion from and to Arabic.
Toolbox Logo

名称

 Math::Roman - ローマ数字とアラビア数字の変換


概要

use Math::Roman qw(roman);

$a = new Math::Roman 'MCMLXXIII';  # 1973
$b = roman('MCMLXI');              # 1961
print $a - $b,"\n";                # 'XII'

$d = Math::Roman->bzero();         # ''
$d++;                              # 'I'  
$d += 1998;                        # 'MCMXCIX'
$d -= 'MCM';                       # 'XCIX'

print "$d\n";                      # "MCMIC"
print $d->as_number(),"\n";        # Math::BigInt "+1999"

稼働環境

 perl5.005, Exporter, Math::BigInt


エクスポート

 デフォルトでは何もエクスポートしないが、as_number(), roman(), error() はエクスポートすることができる。


説明

 どうも私はPerlのローマ数字ウィルス(Perligata-Virus)に感染したようだ。^^;
 本モジュールではローマ数字をあたかも大整数のように計算することができる。数字は任意長でよく、Math::BigInt で使える関数は使うことができる。

入力

 単独のローマ数字は以下の通りである。

I       1
V       5
X       10
L       50
C       100
D       500
M       1000

 以下の規則(きわめて現代的)が適用される。

 I, X, C は3回まで繰り返して使うことができ、V, L, D は1度のみ使うことができる。
 技術的には M は4回まで使うことができるが、本モジュールでは任意の大きさの数を扱うためこの制限がない。
 ローマ数字はトークンから成り立っており、各トークンは IVXLCDM のうちの1文字または2文字から成り立っている。最初の文字は2番目の文字よりも小さい。後者の場合は最初の文字を2番めの文字が示す値から減じる。(たとえば IV は6ではなく4を表す。)
 小さいほうの数字は10のべき乗(I, X, C)で、それ自身の値の10倍より大きくない値の前に来る。小さいほうの値は少なくともその値の10倍より大きい値の前に来て(たとえば LXC は妥当でない)、以下に続く値から1を減じた値よりも大きい値でなければならない(CMD は妥当でない)。
 各トークンはその後のトークンより小さくなければならない(たとえば IIV は I が IV より小さいので妥当でない)。
 入力は確認され、不適であれば 'NaN' を返す。次のローマ数字を生成するまでならその原因を Math::Roman::error() で取得することができる。
 ローマ数字が構成可能な妥当なトークンのデフォルトのリストは以下の通りである。

III     3
XXX     30
CCC     300
II      2
XX      20
CC      200
IV      4
IX      9
XL      40
XC      90
CD      400
CM      900
I       1
V       5
X       10
L       50
C       100
D       500
M       1000

 妥当でないトークンのデフォルトのリストは以下の通りである。

IIII        XXXX            CCCC
DD          LL              VV
C[MD][CDM]  X[LC][XLCDM]    I[VX][IVXLCDM]  

 詳しくは http://netdirect.net/~charta/Roman_numerals.html で確認すべし。

出力

 出力は常に可能な限り短い形式で行われ、トークンは降順に並び替えられる。


規則の変更

 Math::Roman::tokens() を用いて定義されたトークンとその値を取得することができる。-1 とともに返されたトークンは妥当でなく、他は妥当である。フォーマットは token0, value0, token1, value1... という形式である。Math::Roman::tokens() で独自のものを設定し格納することができる。ルーチンは token, value, token, value... という形式の配列であることを想定している。各トークンは単一の文字列または正規表現である。値 -1 は妥当でないトークンであることを示す。
 以下に他のルールはそのままで引き算を削除した(足し算だけが妥当)例を示す。このとき 'XIIII' は 14 となり、トークン集合は再定義され、'AAB' は 25 とパースされる。

use Math::Roman;

Math::Roman::tokens( qw(I 1  V 5  X 10  L 50  C 100  D 500  M 1000));
$r = Math::Roman::roman('XIIII');
print "'$r' is ",$r->as_number(),"\n";
$r = Math::Roman::roman('XV');
print "'$r' is ",$r->as_number(),"\n";
Math::Roman::tokens ( qw(A 10 B 5) );
$r = Math::Roman::roman('AAB');
print "'$r' is ",$r->as_number(),"\n";

 他のアイデアとしては、記号にダッシュをつけることである。これは1,000を乗じることを意味する。これは ASCII で表現することは困難であるので、小文字で表すこととすると以下のようになる。

use Math::Roman;

# まちがって 'M' を定義しないでおくかも知れないが、あまりにも多くの 'M' はスクリーンに適合しない。
print 'old: ',new Math::Roman ('+12345678901234567890'),"\n";
@a = Math::Roman::tokens();
push @a, qw ( v 5000  x 10000  l 50000  c 100000  d 500000  m 1000000 );
Math::Roman::tokens(@a);
print 'new: ',new Math::Roman ('+12345678901234567890'),"\n";

有用なメソッド

new()

new();

 新しく Math::Roman オブジェクトを生成する。引数は、 /^[IVXLCDM]*$/ の形態の(さらなるルールは上を見よ) 'MCMLXXIII' (1973) のような文字列としてのローマ数字である。または Math::BigInt で用いられる文字列による数値である。

roman()

roman();

 new と同じ動きをするが、短いコードでインポートできる。

error()

Math::Roman::error();

 最近の数値生成で結果が NaN であったときのエラーを返す。

bstr()

$roman->bstr();

 あらかじめ設定されたルールに従ったローマ数字の内部における値を表す文字列を返す。0は '' で表される。出力は立つなトークンからなり、符号を含まない。符号が知りたい場合には as_number() を用いる。
 この関数は常に可能な限り最短の形態を生成する。

as_number()

$roman->as_number();

 内部値を示す文字列をアラビア数字として符号をつけて返す。


詳細

 計算を行うには、内部的に Math::BigInt を用いており、全てオーバーロードされる。ローマ数字には0も不の値もないが、本モジュールでは両方とも扱う。ローマ数字では絶対値だけが取得され、sign() または as_number() を用いたときのみ符号を得ることができる。


使用例

use Math::Roman qw(roman);

print Math::Roman->new('MCMLXXII')->as_number(),"\n";
print Math::Roman->new('LXXXI')->as_number(),"\n";
print roman('MDCCCLXXXVIII')->as_number(),"\n";

$a = roman('1311');
print "$a is ",$a->as_number(),"\n";

$a = roman('MCMLXXII');
print "\$a is now $a (",$a->as_number(),")\n";
$a++; $a += 'MCMXII'; $a = $a * 'X' - 'I';
print "\$a is now $a (",$a->as_number(),")\n"; 

制限

内部における数の長さ

 実際の計算には Math::BigInt における制限と同じものが適用される。

出力の長さ

 ローマ数字における出力は、最も大きな数字が 65536 回までに制限されている。これはデフォルトでは 'M' であるので出力可能な最大のローマ数字は 65536000 - であり、これは 64 KBytes の M が並ぶことになるが、誰が本当にこれが必要となるのか?

数の規則

 「各トークンはその前のトークンよりも大きくなければならない」という規則は決定的で打ち勝つことができない。だから 'IIX' を12と再定義しない限り引き算のない数値での 'IIX' は妥当ではない。


バグ

関数のインポート

 badd() のような通常の数学関数を以下のように書いてインポートすることはできない。

use Math::Roman qw(badd);		# これは落ちる。

$a = badd('MCM','M');			# 機能しない。
$a = Math::Roman::badd('MCM','M');	# これも動かない。

 これを機能するようにすることは可能であるが、きわめて大量のコピー&ペーストが必要となり、各計算において大きなオーバーヘッドとなる。実際にはあまり必要でないので以下のようにすればよい。

use Math::Roman;

$a = new Math::Roman 'MCM'; $a += 'M';  # イケてない?
$a = Math::Roman->badd('MCM','M');      # またはこれとか。

トークンとしての '0'-'9'

 new() において 0-9 をトークンとして用いると、0-9 だけからなる引数を与えた際に誤った結果を生成する。これはまず BigInt が構成しようとするためである。


ライセンス

 本プログラムはフリーソフトウェアであり、Perl 本体と同等の条件で修正/再配布してもよい。


著者

 本モジュールをユーザのプロジェクトに利用するなら連絡して欲しい。私のコードがどのように役立っているかを知りたいので。

 Copyright (C) MCMXCIX-MMIV by Tels <http://bloodgate.com/>

Toolbox Logo
Updated : 2007/08/08