整数化の問題 (オレオレ int を作る)

Perlで整数判定がしたい - みずぴー日記「切り捨て」に int() は使うべからず - にぽたん研究所に int() 関数がダメということが書かれていますが、既に7021 != 7021 (Part 3) - ときどきの雑記帖 リターンズ 2006年12月に既に議論されています。

元の数値(70.21*100)に適当なεを加えてやってから整数化するか、整数化する
前とした後の数値を比較してある範囲に入っていたら整数化した数値に 1加えて
やるかすればいいのではないでしょうか?

なので、オレオレ int() 関数を作ってみます。

#! /usr/local/bin/nawk -f
# ore_int.awk

BEGIN {
    print "int(125 ^ (1 / 3))                           = " \
           int(125 ^ (1 / 3));
    print "ore_int(125 ^ (1 / 3))                       = " \
           ore_int(125 ^ (1 / 3));
    print "int(70.21 * 100)                             = " \
           int(70.21 * 100);
    print "ore_int(70.21 * 100)                         = " \
           ore_int(70.21 * 100);
    print "int(0.5005 * 10000)                          = " \
           int(0.5005 * 10000);
    print "ore_int(0.5005 * 10000)                      = " \
           ore_int(0.5005 * 10000);
    print "int(1.0000000000000000000000000001)          = " \
           int(1.0000000000000000000000000001);
    print "ore_int(1.0000000000000000000000000001)      = " \
           ore_int(1.0000000000000000000000000001);
    print "int(0.9999999999999999999999999999)          = " \
           int(0.9999999999999999999999999999);
    print "ore_int(0.9999999999999999999999999999)      = " \
           ore_int(0.9999999999999999999999999999);
    print "int(-1.0000000000000000000000000001)         = " \
           int(-1.0000000000000000000000000001);
    print "ore_int(-1.0000000000000000000000000001)     = " \
           ore_int(-1.0000000000000000000000000001);
    print "int(-0.9999999999999999999999999999)         = " \
           int(-0.9999999999999999999999999999);
    print "ore_int(-0.9999999999999999999999999999)     = " \
           ore_int(-0.9999999999999999999999999999);
}

# 2 進数表記できない場合にある程度丸めた整数化を行います (オレオレ int)
#   in:     数値 num
#   out:    num の整数化されたもの
function ore_int(num,       str, period) {
    str = sprintf("%100.99f\n", num) "";
    ##print str;
    if (abs(sprintf("%100.99f", str - int(num) - get_e())) < 0.000000000001) {
        return int(num) + 1;
    } else {
        return int(num);
    }
}

# 絶対値を返す
#   in:     数値 num
#   out:    数値 num の絶対値
function abs(num) {
    if (num >= 0) {
        return num;
    } else {
        return -num;
    }
}

function get_e() {
    return 0.999999999999999;
}

例として、いくつか標準 int() 関数で問題になりそうなものを集めてみました。 計算結果は以下のようになります。

$ nawk -f ore_int.awk
int(125 ^ (1 / 3))                           = 4
ore_int(125 ^ (1 / 3))                       = 5
int(70.21 * 100)                             = 7020
ore_int(70.21 * 100)                         = 7021
int(0.5005 * 10000)                          = 5004
ore_int(0.5005 * 10000)                      = 5005
int(1.0000000000000000000000000001)          = 1
ore_int(1.0000000000000000000000000001)      = 1
int(0.9999999999999999999999999999)          = 1
ore_int(0.9999999999999999999999999999)      = 1
int(-1.0000000000000000000000000001)         = -1
ore_int(-1.0000000000000000000000000001)     = -1
int(-0.9999999999999999999999999999)         = -1
ore_int(-0.9999999999999999999999999999)     = -1

ここの 0.9999999999999999999999999999 は 1 として扱われているわけですが、どのへんから 1 として扱われているのかを簡単なスクリプトで見てみます。

#! /usr/local/bin/nawk -f
# int_test.awk

BEGIN {
    a = "0.";
    for (i = 1; i <= 100; i++) {
        a = a "9";
        b = a + 0;
        printf("str = %s\n", a);
        printf("num = %51.50f\n", b);
    }
}

実行してみます。

$ nawk -f int_test.awk
str = 0.9
num = 0.90000000000000002220446049250313080847263336181641
str = 0.99
num = 0.98999999999999999111821580299874767661094665527344
str = 0.999
num = 0.99899999999999999911182158029987476766109466552734
str = 0.9999
num = 0.99990000000000001101341240428155288100242614746094
str = 0.99999
num = 0.99999000000000004551026222543441690504550933837891
str = 0.999999
num = 0.99999899999999997124433548378874547779560089111328
str = 0.9999999
num = 0.99999990000000005263558477963670156896114349365234
str = 0.99999999
num = 0.99999998999999994975240724670584313571453094482422
str = 0.999999999
num = 0.99999999900000002828193146342528052628040313720703
str = 0.9999999999
num = 0.99999999989999999172596290009096264839172363281250
str = 0.99999999999
num = 0.99999999998999999917259629000909626483917236328125
str = 0.999999999999
num = 0.99999999999900002212172012150404043495655059814453
str = 0.9999999999999
num = 0.99999999999989996890548127339570783078670501708984
str = 0.99999999999999
num = 0.99999999999999000799277837359113618731498718261719
str = 0.999999999999999
num = 0.99999999999999900079927783735911361873149871826172
str = 0.9999999999999999
num = 0.99999999999999988897769753748434595763683319091797
str = 0.99999999999999999
num = 1.00000000000000000000000000000000000000000000000000
<snip>

つまり 0.99999999999999999 はどうやっても 1 に見なされてしまうため、xgawk の MFPR 機能を使わないとこのあたりが限界のようです。 前述の ore_int() 関数はもう少しゆるめに作ってありますので、うまく機能しない場合は調整してください。

tag_nawk.pngtag_nawk.pngtag_nawk.pngtag_nawk.png