数値を上位桁からの指定桁数で丸める処理を awk で書いてみる
とあるデータの分布状況を把握するために「数値を上位桁からの指定桁数で丸めて切り捨てて」集計しようと思った。やりたいことはこんな感じ。
元の数 | 上位1桁 | 上位2桁 | 上位3桁 | 上位4桁 |
0 | 0 | 0 | 0 | 0 |
1 | 1 | 1 | 1 | 1 |
2 | 2 | 2 | 2 | 2 |
4 | 4 | 4 | 4 | 4 |
8 | 8 | 8 | 8 | 8 |
16 | 10 | 16 | 16 | 16 |
32 | 30 | 32 | 32 | 32 |
64 | 60 | 64 | 64 | 64 |
128 | 100 | 120 | 128 | 128 |
256 | 200 | 250 | 256 | 256 |
512 | 500 | 510 | 512 | 512 |
1024 | 1000 | 1000 | 1020 | 1024 |
2048 | 2000 | 2000 | 2040 | 2048 |
4096 | 4000 | 4000 | 4090 | 4096 |
8192 | 8000 | 8100 | 8190 | 8192 |
16384 | 10000 | 16000 | 16300 | 16380 |
32768 | 30000 | 32000 | 32700 | 32760 |
65536 | 60000 | 65000 | 65500 | 65530 |
131072 | 100000 | 130000 | 131000 | 131000 |
262144 | 200000 | 260000 | 262000 | 262100 |
524288 | 500000 | 520000 | 524000 | 524200 |
単純な丸めなら int, roundup, roundown と桁数の割り算で済ませてしまえばよいのだけど、このパターンだとそういうわけにもいかない。
そこで4つほど方法を考えてみました。このうち「数学的に見ても正しいロジック」は元の値が整数か否かによらず正しい計算ができます。その他のロジックは手抜きなので、値が整数の場合だけ正しい結果が出るはずと思います
#!/usr/bin/awk -f BEGIN { # 丸める桁数 unit=3 } { print round1($1, unit) print round2($1, unit) print round3($1, unit) print round4($1, unit) } # 数学的に真面目に書いたロジック。 function round1( num,unit ,floornum, num_len, uppernum, retval, abs ) { # 数値がゼロならゼロを返す if ( num == 0 ) retval = 0 else { # マイナスの値はプラスとして扱った上で最後に補正する if ( num < -0 ) { abs = -1 ; num=-num } else { abs = 1 } # 底の変換公式を用いて、num = 10^x の x を求める # この値の小数部分を切り上げると数値の桁数になる floornum = log(num)/log(10) # 丸める桁数を求める(数値の桁数から unit を引く) num_len = ( int(floornum) + 1 ) - unit # 元の数値に対して、丸める桁数分だけ小数点を左にずらした上で、小数点以下を捨てる uppernum = int(num/(10^num_len)) # 小数点以下を捨てた値の小数点を元に戻す。元の数値がマイナスなら符号も戻す。 retval = abs * uppernum*10^num_len } return retval} } # 基本的な演算は round1() と同じだけど、桁数の算出は「文字を数える」方法で逃げるパターン function round2( num,unit ,num_len ) { num_len = num >= 0 ? length( num ) - unit : length( num ) - unit - 1 return int(num/(10^num_len))*10^num_len } # round2()の変形パターン。 # 数値を文字列で扱い、残す部分を substr で抜いた上で削る部分の桁数を補っている。 # ただし元の数値の大きさが、残す桁数より小さい場合は計算がおかしくなる。 # そこで三項演算子で場合分けして逃げる。 function round3( num,unit , num_len, retval ) { unit = num >= 0 ? unit : unit + 1 retval=length(num) < unit ? num : substr( num,1, unit )*10^(length(num) - unit) return retval } # もっとも手抜きな方法。 # 元の数字から、丸め対象の数値を抜き出して引き算する。 # 変数の型指定が無い処理系ならではの手抜きぶりだけど、シンプルといえばシンプル。 # ただし小数を含む値を想定していないので、整数値の集計だけに使える方法。 function round4( num,unit ,retval ) { retval = num > 0 ? num - substr(num,1 + unit) : num + substr(num,2 + unit ) return retval }