■ 問題
例えば、10円をなるべく 11:12:27 (計50) に近い比率で按分したいとする。比率を掛けて丸めると以下のようになる
10 * (11 / 50) = 2.2 ≒ 2円 10 * (12 / 50) = 2.4 ≒ 2円 10 * (27 / 50) = 5.4 ≒ 5円小数部分を合計すると 10円だが、丸めた整数部分を計算すると 9円で 1円消えてしまう。この 1円をどうするか。
◆ ① 誤差を許容する
仕様によっては、これもあると思う。検索してみると、そういう但し書きのある公官庁発表のデータもいくつか見つかる。
◆ ② 誤差を最大値に割り当てる
上例の場合、5 円に誤差 1円を加算して 2円、2円、6円とする。ロジック的には、どこに誤差を散らすか決定するために、余計に一手間二手間かかるはず。
◆ ③ 累積値で丸める
以下のように累積値で四捨五入して、、、
10 * (11 / 50) = 2.2 ≒ 2 → 2 - 0 = 2円 10 * (23 / 50) = 4.6 ≒ 5 → 5 - 2 = 3円 10 * (50 / 50) = 10.0 ≒10 → 10 - 5 = 5円、、2円、3円、5円となる。
■ 比較
3つの方法で得られた値は、以下のように比較できる
元の比率 | 11 | 12 | 27 | 計=50 |
---|---|---|---|---|
① | 10 | 10 | 25 | 計=45 |
② | 10 | 10 | 30 | 計=50 |
③ | 10 | 15 | 25 | 計=50 |
((元-①)/元)^2 | 0.0083 | 0.0278 | 0.0055 | 0.0415 |
((元-②)/元)^2 | 0.0083 | 0.0278 | 0.0123 | 0.0484 |
((元-③)/元)^2 | 0.0083 | 0.0625 | 0.0055 | 0.0763 |
累積値で丸める ③は比率を構成する数値の並び方で値が変わってくるはずで、この例だけではなんとも言えないが、計算量は少なくてすみそう。
最大値に誤差を割り当てた ②が、元の比率に近い。直感的には、この ②のパターンが、もとの比率を一番よく反映していそう。細かいことを考えると、この手法を採った場合、同じ比率の場合に、どれに端数を割り当てるかという問題が残る。例えば 3円を2人で分けたり、1円を2人で分けたりする場合など。
■ 実装
使用箇所を Java で書いた場合を想像するとこんな感じか
Proration proration = new Proration(new int[] {11, 12, 27}); int[] actual = proration.prorate(10); int[] expected = {2, 2, 6}; assertArrayEquals(expected, actual);まあ「関数型言語だったらアレなのになあ」なんてボヤきながら書くような、ベタな Java コーディングになりそうだ。
単に業務ロジックの実装として書くとしたら、普通に必要なコードだけ書けば良いだろうけど、フレームワークとかライブラリにしようとしたら、以下のような事が考慮するポイントになるかもしれない。
- 複数の型で generic に使えるようにする。
- 端数割り当てロジックを実行時に指定できるようにする。
- ②で、端数の割り当て先選択ロジックを実行時に指定できるようにする。
■ 所感
検索したら、すぐに定番的なやり方が見つかるかと思ってたら、そうでもなかった。
そもそも案分を英語でなんて言うのかわからなかったりしたが、prorate と言う単語を見つけてググると、とりあえずこんなのを見つけた。「lossless」な proration アルゴリズムについての質問とその一個の解答が載ってた。
0 件のコメント:
コメントを投稿