hnwの日記

Rubyのround関数の実装が変わってた

今週は国際浮動小数点数ウイークですので、週の最後もやはり浮動小数点数ネタです。


Rubyのround関数の実装に関して、1.8.6 patchlevel 114と1.8.6 patchlevel 230の間に変更が入っています。1.8.6-p230のリリース時期は2008/06/20のようですから、1.8.7(2008/06/01リリース)から変更されたと言っていいかもしれません。


これらは、僅かながら挙動が違っています。MacOSX環境で実験しました。(再現できるのはFreeBSDMacOSXだけのような気がします)

$ /usr/bin/ruby --version
ruby 1.8.6 (2008-03-03 patchlevel 114) [universal-darwin9.0]
$ /usr/bin/ruby -e 'p 9007199254740991.0.round;'
9007199254740992
$ /opt/local/bin/ruby --version
ruby 1.8.7 (2008-08-11 patchlevel 72) [i686-darwin9]
$ /opt/local/bin/ruby -e 'p 9007199254740991.0.round;'
9007199254740991


なんと、同じ数字の丸め結果が違う数字になりました。中身を簡単に説明すると、前者は自前実装していたのが、後者はlibmのroundに丸投げしています。roundが無い環境のために自前のroundも用意してありますが、これも1.8.6-p114以前とは異なる実装です。


1.8.6-p114の方が不思議な挙動に見えますので、修正自体は良いと思います。とはいえ、PHP以外の言語でも、マイナーバージョンアップで基本的な関数の実装をいじることがあるんですね。その点は少しビックリしました。

実装の違い

では、Cソースコードを見てみましょう。まずは1.8.6-p114のソースコードから。

static VALUE
flo_round(num)
    VALUE num;
{
    double f = RFLOAT(num)->value;
    long val;

    if (f > 0.0) f = floor(f+0.5);
    if (f < 0.0) f = ceil(f-0.5);

    if (!FIXABLE(f)) {
        return rb_dbl2big(f);
    }
    val = f;
    return LONG2FIX(val);
}


この通り、自前で実装しています。1.8.7からは下記のようになりました。

#ifndef HAVE_ROUND
double
round(x)
    double x;
{
    double f;

    if (x > 0.0) {
        f = floor(x);
        x = f + (x - f >= 0.5);
    }
    else if (x < 0.0) {
        f = ceil(x);
        x = f - (f - x >= 0.5);
    }
    return x;
}
#endif
static VALUE
flo_round(num)
    VALUE num;
{
    double f = RFLOAT(num)->value;
    long val;

    f = round(f);

    if (!FIXABLE(f)) {
        return rb_dbl2big(f);
    }
    val = f;
    return LONG2FIX(val);
}


1.8.7では、round関数が無い環境のみ自前実装を使うようになっています。実際のところ、roundが無い環境はWindowsくらいでしょうか。自前実装についても以前とは微妙に計算順序が変わっており、環境次第では異なる結果になる可能性があるように思います。

背景とか

この変更が入った原因は [ruby-dev:32247] あたりの議論にあるようですね。「なあに、かえって精度が増す」みたいな話も出ていますけど、後方互換性は気にしなくていいのか、人ごとながら気になりました。


また、ライブラリに丸投げしてしまうと、環境によって挙動の差が出ることがあります。これを嫌って、PHPではライブラリコールしていたのを自前実装に切り替えたりしている例もあります。その意味でも、自前実装をライブラリコールに切り替えて大丈夫なのかな、という気がします。

最後に

そんな変な週はおそらく無いと思います。