pslaboが試したことの記録

はてなダイヤリーからはてなブログに引っ越してきました

この日記は現在実行中の減量記録を含む個人的なメモとして始めましたが、最近はコンピュータやガジェット、ハック、セキュリティネタのほうがメインになっております。

はてなダイヤリー時代はカテゴリ分けが適当だったのですが、これはそのうち直します。


数値を上位桁からの指定桁数で丸める処理を 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
}