長さの単位を扱う

class Length
	YARD = {
		:barleycorn => ((1.0 / 3) / 12) / 3,
		:in         => (1.0 / 3) / 12,
		:ft         => 1.0 / 3,
		:yard       => 1.0,
		:pole       => 5.5,
		:chain      => 5.5 * 4,
		:furlong    => 5.5 * 4 * 10,
		:ml         => 5.5 * 4 * 10 * 8,
		:league     => 5.5 * 4 * 10 * 8 * 3,
	}

	METRIC = {
		:Ym  => 10 ** 24,
		:Zm  => 10 ** 21,
		:Em  => 10 ** 18,
		:Pm  => 10 ** 15,
		:Tm  => 10 ** 12,
		:Gm  => 10 ** 9,
		:Mm  => 10 ** 6,
		:km  => 10 ** 3,
		:hm  => 10 ** 2,
		:dam => 10,
		:m   => 1,
		:dm  => 10 ** -1,
		:cm  => 10 ** -2,
		:mm  => 10 ** -3,
		:um  => 10 ** -6,
		:nm  => 10 ** -9,
		:pm  => 10 ** -12,
		:fm  => 10 ** -15,
		:am  => 10 ** -18,
		:zm  => 10 ** -21,
		:ym  => 10 ** -24,
	}
	UNITS = YARD.keys + METRIC.keys
	RATIO = 0.0254

	YARD.each do |unit, ratio|
		define_method(unit) {
			self.new(self.to_yard.number * ratio, unit)
		}
	end

	METRIC.each do |unit, ratio|
		define_method(unit) {
			self.new(self.to_metric.number * ratio, unit)
		}
	end

	def initialize(num, unit)
		raise "Unsupported units!" unless UNITS.include?(unit)

		@number = num
		@unit = unit
	end

	def to_metric
		if self.is_metric?
			self
		else
			new(self.inch.number * RATIO, :m)
		end
	end

	def to_yard
		if self.is_yard?
			self
		else
			new(self.m.number / RATIO, :inch)
		end
	end

	def is_metric?
		METRIC.keys.include?(@unit)
	end

	def is_yard?
		YARD.keys.include?(@unit)
	end

	private
	def number
		@number
	end

	def unit
		@unit
	end

end

class Numeric
	Length::YARD.merge(Length::METRIC).each do |unit, ratio|
		define_method(unit) {
			Length.new(self * ratio, unit)
		}
	end
end

なんでもいいから、定期的にコードを書く習慣をつけたくて、書いてみた。きっと既にあるんだろうけど、筋トレみたいなものだし、知らないふりをする。
いろいろバグありそう。長さの単位を表すメソッド (cm とか yard) がとても短いのでいろいろよくない。実質的な計算は定数の中でほぼ済んでいるという、なんとも奇妙なコード。
Ruby で書いたコードは文字列処理とかメタプログミングばかりで、数値は四則計算くらいしか触ったことがなかった。べき乗の演算子って ^ だっけ、などとおもったらビットシフトだったでござる。
Wikipedia で単位系を調べるのに一番時間を費したという。

TODO:

  • <=> メソッドを実装する。 Comparable を Mix-in するのに必要。
  • 四則演算への対応。