それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

年齢を計算する

年齢計算とは、単純だけどちょっと難しいものです。

色々と考えられるけど、判断していいのか困ったのであれこれ考えた。

日数から、年が何回出てるか(365の商)を見る

剰余は無視すればい。

(Date.parse('2022-12-01') - Date.parse('1989-12-02')).to_i/365

閏年

閏年(LEAP)の問題が発生するので、1年を365日と1/4日にしてみる。

(Date.parse('2022-12-01') - Date.parse('1989-12-02')).to_i/365.25

まぁこれでも十分な精度が得られる。

それでいいの?

人間の年齢ならね。人間の年齢は0−99まで計算できればいので有効桁数が2桁である。 1年を365日で計算していると、0.25/365だけ誤差が出てくる。有効桁数を考えれば無視できるかな。 365.25で、小数点以下を切り捨てるので、有効桁数を考慮しても十分な精度である。

ただし、人間の年齢で、かつ「日付」を使うならね。

この考え方を応用すると、

((Date.parse('2022-12-02') - Date.parse('1989-12-02'))/365.25)=32.99931553730322

上記のように、32歳と11ヶ月30日だとしても、だ。32.9999 になっても切り捨てるので問題ない。

日数(365日)による計算がいやなら

もう少しマシな方法を考えよう。

こちらは、いったん数え年(序数)で出しておいて

x_th_year = Date.parse('2022-12-02').year-Date.parse('1989-12-02').year #=>33

満年齢に達していないときは、減らせばいい。

x_th_year - 1 if (Date.parse('2022-12-01').prev_year(33) - Date.parse('1989-12-02'))   #=> 32

if文が、めんどくさい。

if が持ってるフラグ部分をなんとかしたい 、年齢は(2022-1989)-(1 or 0 )であるとわかるので、-1の部分を入れればいいとわかる。

数え年は次のように計算して

( Date.parse('2022-12-01').strftime('%Y').to_i - Date.parse('1989-12-02').strftime('%Y').to_i ) #=> 33

これが、0.01 でも減ればいい。

なので、数字を次のようにみると

Date.parse('2022-12-01').strftime('%Y,%m%d') #=> 2022,1202
Date.parse('1989-12-02').strftime('%Y,%m%d') #=> 1989,1201

数え年に、誕生日のプラスマイナスを加減してあげて、INTをとれば十分な精度だと解る。

1年は365日なので、4桁くらい用意しておけば十分である。

33*1000 - (1202-1201) #=> 32999

4桁足したので、4桁を減らしてINT取ればいい。

(33*1000 - (1202-1201))/1000 #=> 32.999
((33*1000 - (1202-1201))/1000).to_i #=> 32

これで十分である。

この計算をまとめると次のようになる。

(2022*1000+1201 - 1989*1000+1202) / 1000 

この計算を整理すると、単純に文字列を「8桁の数字」として計算しているだけである。

1202は4桁なので、5桁で割って、小数点に落としてしまえば、INTとして計算ができる。

(20221201 - 19891202) / 10000

したがって、年齢計算を簡単にやるには、次で十分である。

(Date.today.strftime('%Y%m%d').to_i- Date.parse('1989-12-01').strftime('%Y%m%d').to_i ) /10000

これは、有名なアルゴリズムであるね。

10000で割るというものの別解

べつにさ、0.001だけでも減ってしまえば、丸めていいんだから。INTで計算してからINTに丸める必要はない。

小数点を使って計算すればいいじゃん

2022.1201 - 1989.1202

なので、こうなる。

today =  Date.today.strftime('%Y.%m%d').to_f #=> 2022.1201
birthday = Date.parse('1989-12-01').strftime('%Y.%m%d').to_f #=> 1989.1202
( today - birthday ).to_i

一行で書くと

( Date.today.strftime('%Y.%m%d').to_f - Date.parse('1989-12-01').strftime('%Y.%m%d').to_f ).to_i

int 丸める処理の直前で、桁を上げる必要すらないことがわかる。

昔の計算機は貧弱だったのでFloatの計算を避たのだろう。

現代っ子にとっては、小数点を使ったほうがわかりやすいだろう。

年齢計算ニ関スル法律

ちなみに年齢計算ニ関スル法律というものがあり、年齢計算のアルゴリズムは法律で定められている。

だた、法律の年齢計算のアルゴリズムを厳密にやると大変なので、どこかで近似してざっくり計算しても十分なことが多い。

だって、欲しいものは整数値(INT)だもの。

今回の計算は日付と日数を単純に計算したものです。

Date型を使わなくても十分だったり。

年齢計算とは、単純な桁繰り上げ問題ということでした。

Date型は内部でINT秒を変換してる判断してると、こういうざっくりした計算や時刻比較が思いつかないので困るよね。

たとえば、日付の比較だって単純な数字の比較できる。2022.1201 > 2022.1130 なのですから。

他にも日付の検索を単純に考えることも出来る。

たとえば、文字列で検索とか、数値で検索とかできちゃうよね。

select * from table where birthday LIKE '1988-11-%' # 文字列で1989-11-05 のように入れてた場合
select * from table where birthday > 1988.1100  # 数値で、1989.1105のように入れてた場合。

日付とか年齢ってDate型で扱うのが正義だけど、Date型がいつでも最短経路だとは限らない。

(現代のコンピュータはUTC/JSTなど時差を考慮するから、Date型が必須なんだけどね。)