Rubyで単位付き数値ライブラリ

2014/11/18更新

gem化しました。
`gem install numeric_with_unit`
でインストールできます。

----

#単位付き数値とは

- 1000 [m]
- 1800 [s]
- 5000 [kg]

のような概念です。これらは数値の他に、単位の情報を持っています。
したがって次元が同じならば別の単位に変換することができます。

- 1 [km]
- 0.5 [hr]
- 5 [ton]

そしてこれらは掛けあわせたりすることで、組立単位にすることもできます。

- 1 [km] / 0.5 [hr] = 2 [km/hr]
- 1 [kg] * 9.8 [m] / (1 [s])^2 = 9.8 [kg.m/s2] = 9.8 [N]

#ruby-numeric-with-unit

上記のような計算がなるべく自然にできるライブラリを作成してみました。

https://github.com/diaphragm/ruby-numeric-with-unit

こんな感じに計算ができます。

```ruby:example.rb
require 'numeric_with_unit'
NumericWithUnit.new(1, 'm') # 1[m]を意味するオブジェクト
1.to_nwu('m') #Numeric#to_nwuを用いてもOK

# 1['m']のように自然に書くために、Numeric#[]をオーバーライドするファイルを読み込みます。
require 'numeric_with_unit/util'

length = 1000['m']
puts length #=> 1000 m
puts length['km'] #=> 1 km

time = 1800['s']
puts time #=> 1800 s
puts time['hr'] #=> 1/2 hr

speed = length / time
puts speed #=> 5/9 m/(s) # = 0.5555555555555556
puts speed['km/hr'] #=> 2 km/(hr)

# 次元が同じならば足し算引き算もできます。
speed_2 = 300['ft'] / 10['min']
puts speed + speed_2 #=> 15929/22500 m/(s) # = 0.7079555555555556

```

単位変換の厳密さを向上させるため、Rational型で計算しています。

##もう少し詳しく
###単位のフォーマット
- m
- cm
- m2
- kW.hr
- kg/cm2
- m.s-1
- J/kg/K
- kcal/(hr.m2.℃)

のように、先頭に接頭辞(kやc)、末尾に指数を付けた基本単位を"."または"/"で繋げた形で表記します。
基本単位が定義されていれば、組立単位は自動的に導出されます。

###単位の定義方法
単位はUnitクラスで表現します。
基本的な単位(SI)は`nwu/base_unit.rb`で定義されており、SI単位から派生する単位は次のように定義できます。

```ruby:unit_definition.rb
# [kg],[m],[s]は定義済みとする。
NumericWithUnit::Unit['N'] = 'kg.m/s2' #newtonの定義
NumericWithUnit::Unit['Pa'] = 'N/m2' #pascalの定義
NumericWithUnit::Unit['ton'] = 1000, 'kg' #tonの定義
```

基本単位として定義しておけば、接頭辞がついた単位([cm]など)、指数がついた単位([m2]など)や、基本単位で表現できる組立単位([m/s]など)をいちいち定義する必要はありません。

上記の例で言うと、[Pa]を定義しているので[kPa]や\[Pa.s](粘度の単位)を定義する必要はありません。

一般的と思われる単位は、`numeric_with_unit/common_unit.rb`でひと通り定義してあります。
`NumericWithUnit::Unit.list`で、定義されている単位の一覧が得られます。

##具体的な使用例

次のような問題を単位換算の必要なしに、素直な記述で計算できます。

###濃度の計算

> モル質量40[g/mol]の物質5[mg]を250[cc]の水に溶かした時のモル濃度[mol/L]は？

```ruby:ex_concentration.rb
require 'numeric_with_unit/util'

molar_mass = 40['g/mol']
mass = 5['mg']
volume = 250['cc']
concentration = (mass / molar_mass) / volume

puts concentration['mol/L']       #=> 1/2000 mol/(L)
puts concentration['mmol/L'].to_f #=> 0.5
```

###消費電力の計算

> 600[W]の電子レンジで4分30秒加熱してカレーメシを作った時の消費電力量[kWh]は？

```ruby:ex_curry_meshi.rb
require 'numeric_with_unit/util'

energy = 600['W'] * (4['min'] + 30['s'])
puts energy['kW.hr']      #=> 9/200 hr.kW
puts energy['kW.hr'].to_f #=> 0.045
```

###流量の計算

> 直径8[in]の配管内を線速度1.5[m/s]で密度988[m3/kg]の液体が流れるときの質量流量[ton/hr]は？

```ruby:ex_mass_flowrate.rb
require 'numeric_with_unit/util'

diameter = 8['in']
velocity = 1.5['m/s']
density = 988['kg/m3']

area = (diameter / 2)**2 * Math::PI
volume_frowrate = area * velocity
mass_frowrate = volume_frowrate * density

puts mass_frowrate['ton/hr'] #=> 173.01668751878202 ton/(hr)
```


##課題

###温度など、n倍で表せない単位換算の仕様
例えば`4.2[J/K] * 1[℃]`を計算する場合、結果は 4.2*1=4.2 にすべきなのか、1[℃]を274.15[K]に変換してから 4.2*274.15=1151.43 とすべきなのか、単位の情報だけでは判断ができません。
このような場合にどいういう処理にするか仕様が未定です。

~~「運用で回避してください」で逃げたい。~~

###パーサーがなんかおかしい
文字列で表された単位をUnitクラスに変換するパーサーに、意図しないフォーマットを渡された場合色々おかしくなるのでそのうち直す

###gem化
~~そのうちやります~~
やりました。
`gem install numeric_with_unit`
でインストールできます。

###ドキュメント整備

###テスト整備


#おわりに
単位換算するツールは世の中に腐るほどありますが、単位の情報を持ったまま計算ができるものは珍しいのではないかと思います。

まだまだ作りこみが甘いですが、私の使う範囲では十分実用的になったためQiitaに投稿してみました。

ツッコミなどありましたら、コメントいただければ幸いです。

##おまけ
irbを関数電卓代わりに使っていて少しでもタイプ数を減らしたい人向け。
`method_missing`を悪用しています。

```ruby:util2.rb
require 'numeric_with_unit/util2'

x = 1000.m # 1000[m]
t = 1800.s # 1800[s]
v = (x/t).km_hr # 2[km/hr]

vis = 1.Pa.s # 1 [Pa.s]
puts vis.cP #=> 1000 cP

puts 0.degC.degF #=> 32 degF
```

##インスパイア元

単位付き数値という言葉は、
http://www.slideshare.net/mrkn/measure-presentation
から頂きました。
こちらは単位換算を、既知の換算式から幅優先探索で見つけていたりとてもチャレンジングです。(私のものは一度SI単位に変換するという泥臭い方法です。)
今はもう開発されていないようで残念です。