冬言響 / 日記

アメコミとか映画とか音楽とか猫とか単車とか自転車とか革とか銀とかジーンズとかブーツとか今日喰ったものとか。

RSS2.0

漢数字 to アラビア数字

二〇〇五年十一月って表記はおかしくないか?:アルファルファモザイク

一つの文章、一つのサイト、或いは一冊の本の中でルールが統一されてれば良いと思うよ、とかなんとか。基本的に〇?九の数字だけでアラビア数字と同じノリで表記しつつ、10?19 だけは「十」?「十九」のようにする、ってのは割とよくある流派だとは思うけど。

自分は漢数字で書く用事があったら基本的に〇?九の文字だけを使って 「二〇〇五年一一月」って書くかな。ルールはシンプルな方が良いから。でもあんま桁が多くなると識別がし難くなるから「万」とか「億」とかは使う。「一〇〇万円」とか「一億二〇〇〇万人」とかみたいに。

全部を〇?九の文字だけで表現してしまえば機械的な数値変換がラク。だが読み取りやすさを優先しようとすればある程度は他の漢字も使いたい。どうにか漢字交じりの表記でも機械的に数値変換出来んものか。「二〇五〇」でも「二千五十」でも同じように「2050」という値を得られんだろうか。

という訳で作ってみた。

  1. 〇以上の整数にのみ対応。小数とか負の数とかは知らん。
  2. 二〇千(=二万)のようなイレギュラーな表現には非対応。
  3. 「一億万」のような間違った表現にも非対応。どんな結果が出るかは知らん。
  4. おそらく探せばもっとシンプルでスマートで効率的なライブラリがいくらでも見つかるのだろうけど、ここは自分で考えてやってみる、てのが重要てことで一つ。

という訳ではじまりはじまりー、と。

function ksj2asj($strIn) {
// シングルバイト文字に置換。
foreach(array(
		"〇" => "0", "一" => "1", "二" => "2", "三" => "3",
		"四" => "4", "五" => "5", "六" => "6", "七" => "7",
		"八" => "8", "九" => "9", "十" => "A", "百" => "B",
		"千" => "C", "万" => "D", "億" => "E", "兆" => "F"
	) as $key => $value) {
	$strIn = mb_ereg_replace($key, $value, $strIn);
	}

シングルバイト文字の方が何かと都合が良いかと思うので変換してみんとす。〇?九はそのまま、「十」「百」「千」などの文字は A から順にアルファベットを割り当ててみる。とりあえず「兆」までだけどこのまま「無量大数」までいっても余裕がある。そんなデカい数値が扱えるのかどうかは知らんが。

仮に例えば「二億三百六万一〇四一」とかいう表記ルールに統一性の無い文字列が入力された場合、「2E3B6D1041」という文字列になる。

// 桁テーブル
$aryDigit = array(
	"A" => 10,
	"B" => 100,
	"C" => 1000,
	"D" => 10000,
	"E" => 100000000,
	"F" => 1000000000000
	);

それぞれのアルファベットが何桁かを表す配列。後で使う。

// 4桁ごとに区切って配列に格納する。
for($i = 0; $i < strlen($strIn); $i++) {
	$strTmp .= $strIn{$i};
	if(preg_match("/^[D-Z]$/", $strIn{$i})) {
		$aryTmp[] = $strTmp;
		$strTmp = "";
		}
	}
// 最後。万より下の位。
if($strTmp) {
	$aryTmp[] = $strTmp;
	}
// $strTmp を初期化。
$strTmp = "";

漢字文化圏の数字は 4 桁ごとに区切って数詞を付ける方式なので、「万」「億」「兆」…で区切って配列にする。具体的には文字列を 1 文字ずつ見ていって D 以降のアルファベットが出てきたらそこまでを 1 つの配列項目とする。

$strTmp は後で使うので初期化しておく。てか何かと変数名に「Tmp」使いすぎ >俺。

「2E3B6D1041」は「2E」「3B6D」「1041」の 3 つの要素を持つ配列になる。

foreach($aryTmp as $ent) {
	// 最後の 1 字が「D」以降のアルファベットである場合
	// 桁テーブルを参照して値を $intDigit に格納してから
	// その文字を除去する。
	if(preg_match("/[D-Z]$/", $ent)) {
		$intDigit = $aryDigit[substr($ent, strlen($ent) - 1, 1)];
		$ent = substr($ent, 0, strlen($ent) - 1);
		}
	// 最後の 1 字が「D」以降のアルファベットでない場合
	// (1?1000 の位である場合)
	// $intDigit に 1 を入れておく。
	else {
		$intDigit = 1;
		}

それぞれの配列要素は最大 4 桁の数値。それぞれ整数型に変換する。

最後の 1 字が万以上の桁を表すアルファベットであれば、それで先の桁テーブルを参照してその値を保持し、その字を削る。「2E」は「100000000(一億)」を保持して「2」に、「3B6D」は「10000(一万)」と「3B6」になる。「1041」はそのままで「1」を桁数として保持する。

	// 残った文字列を数値に変換する。
	// $ent を 1 文字ずつ見ていく。
	for($i = 0; $i < strlen($ent); $i++) {
		// 0?9 の数値であったら $strTmp に加える。
		if(preg_match("/^[0-9]$/", $ent{$i})) {
			$strTmp .= $ent{$i};
			}
		// A?C のいずれかの文字であったら
		// その文字で桁テーブルを参照して得られた値で
		// その時点での $strTmp を倍算して $intTmp に加算する。
		if(preg_match("/^[A-C]$/", $ent{$i})) {
			// この時点で $strTmp が空だったら「1」を入れておく。
			if(!$strTmp) {
				$strTmp = "1";
				}
			$intTmp += (int)$strTmp * $aryDigit[$ent{$i}];
			// $strTmp を初期化する。
			$strTmp = "";
			}
		// $ent の最後の文字まで来ていたら
		// 残った $strTmp を $intTmp に加算して
		// それをさらに $intDigit で倍算して $intReturn に加算する。
		if($i == strlen($ent) - 1) {
			$intTmp += (int)$strTmp;
			$intReturn += $intTmp * $intDigit;
			// $strTmp と $intTmp を初期化する。
			$strTmp = $intTmp = "";
			}
		}
	}
return $intReturn;
}

桁を表すアルファベットを除去した残りを最大 4 桁の数値に変換する。「2」は「2」のまま。「3B6」は 3 × 100 + 6 で「306」になる。「1041」は「1041」のまま。

それら数値を、先に保持した桁数で倍算して、$itnReturn に加算。2 × 1 億 + 306 × 1 万 + 1041 = 203061041。その数値を返す。

ksj2asj_test.php

もっとこう、正規表現とか使ってバシっと短くいけそうな気もするんだがとりあえず動いてるぽいから良いか。

pre 要素内のソースコードを色分けして出力する仕組みとか作ったので無駄に使ってみたかっただけだったりとかなんとか。

いずれ気が向いたら改良版を考えてみよう。