[ruby-dev:49548] Re: Integer#pow の提案

From: Yugui <yugui@...>
Date: 2016-03-30 13:40:05 UTC
List: ruby-dev #49548
Yuguiです。

細かい実装上のコメントはさておき、まずインターフェースについて伺いたい点があります。
この演算はmodpowと呼ばれることも多いと思いますが、Integer#modpowよりもInteger#powのほうが良い理由は何でしょうか。

個人的には、1引数のInteger#pow(n)と異なりnは整数でなければならない点で結構異なるので、別の名前であったほうが良いと思いました。

On Wed, Mar 30, 2016 at 6:24 PM KISHIMOTO, Makoto <ksmakoto@dd.iij4u.or.jp>
wrote:

> きしもとです
>
> 暗号の計算などで (a**b)%m を求めたいことがあります。この式のまま書くと、
> Bignumがありますのでオーバーフローの問題はありませんが、随時 mod m を
> 取れば本来必要のない重い計算を省いて軽くできますし、どうせなら組込みで
> 計算できれば高速化もでき便利だと考え、Integer#pow として実装しました。
> (メール末に差分を付けました)
> コメント等ありましたらよろしくお願いします。修正の上でプルリクエスト化の
> 予定です。
>
> 実引数の型や値によって例外とする場合などについては Python の組込み関数
> pow を参考に( https://docs.python.org/3/library/functions.html#pow )
> しました。
>
> 可変個引数のメソッドとなっていて、1引数の場合は、そのまま ** を呼び出し
> ます。2引数の場合は、Fixnum(long)で高速に計算できる場合は高速に、
> そうでない場合は汎用の実装で計算します。
>
> コメントでXXXを付けてある箇所は、プルリクエスト化前に改良したい箇所で、
>
> ・Fixnumが31ビットか63ビットか、の振り分け
> ・最近のコンパイラでは /*NOTREACHED*/ のおまじないが効かないようなので
> その代替はどのようにするのがベストプラクティスでしょうか?
>
> という感じです。
>
> diff --git a/numeric.c b/numeric.c
> index 4bb2a32..c5c7afb 100644
> --- a/numeric.c
> +++ b/numeric.c
> @@ -108,6 +108,8 @@ static VALUE fix_mul(VALUE x, VALUE y);
>  static VALUE int_pow(long x, unsigned long y);
>  static VALUE int_cmp(VALUE x, VALUE y);
>
> +static VALUE int_pow2(int argc, VALUE* argv, VALUE x);
> +
>  static ID id_coerce, id_div, id_divmod;
>  #define id_to_i idTo_i
>  #define id_eq  idEq
> @@ -3550,6 +3552,174 @@ int_cmp(VALUE x, VALUE y)
>      }
>  }
>
> +/* Integer#pow */
> +
> +static VALUE
> +int_pow_tmp1(VALUE x, VALUE y, long mm, int nega_flg)
> +{
> +    long xx = FIX2LONG(x);
> +    long tmp = 1L;
> +    long yy;
> +
> +    for (/*NOP*/; ! FIXNUM_P(y); y = rb_funcall(y, idGTGT, 1,
> LONG2FIX(1L))) {
> +        if (RTEST(int_odd_p(y))) {
> +            tmp = (tmp * xx) % mm;
> +        }
> +        xx = (xx * xx) % mm;
> +    }
> +    for (yy = FIX2LONG(y); yy; yy >>= 1L) {
> +        if (yy & 1L) {
> +            tmp = (tmp * xx) % mm;
> +        }
> +        xx = (xx * xx) % mm;
> +    }
> +
> +    if (nega_flg && tmp) {
> +        tmp -= mm;
> +    }
> +    return LONG2FIX(tmp);
> +}
> +
> +static VALUE
> +int_pow_tmp2(VALUE x, VALUE y, long mm, int nega_flg)
> +{
> +    long tmp = 1L;
> +    long yy;
> +#ifdef DLONG
> +    DLONG const mmm = mm;
> +    long xx = FIX2LONG(x);
> +
> +    for (/*NOP*/; ! FIXNUM_P(y); y = rb_funcall(y, idGTGT, 1,
> LONG2FIX(1L))) {
> +        if (RTEST(int_odd_p(y))) {
> +            tmp = ((DLONG)tmp * (DLONG)xx) % mmm;
> +        }
> +        xx = ((DLONG)xx * (DLONG)xx) % mmm;
> +    }
> +    for (yy = FIX2LONG(y); yy; yy >>= 1L) {
> +        if (yy & 1L) {
> +            tmp = ((DLONG)tmp * (DLONG)xx) % mmm;
> +        }
> +        xx = ((DLONG)xx * (DLONG)xx) % mmm;
> +    }
> +#else
> +    VALUE const m = LONG2FIX(mm);
> +    VALUE tmp2 = LONG2FIX(tmp);
> +
> +    for (/*NOP*/; ! FIXNUM_P(y); y = rb_funcall(y, idGTGT, 1,
> LONG2FIX(1L))) {
> +        if (RTEST(int_odd_p(y))) {
> +            tmp2 = rb_fix_mul_fix(tmp2, x);
> +            tmp2 = rb_int_modulo(tmp2, m);
> +        }
> +        x = rb_fix_mul_fix(x, x);
> +        x = rb_int_modulo(x, m);
> +    }
> +    for (yy = FIX2LONG(y); yy; yy >>= 1L) {
> +        if (yy & 1L) {
> +            tmp2 = rb_fix_mul_fix(tmp2, x);
> +            tmp2 = rb_int_modulo(tmp2, m);
> +        }
> +        x = rb_fix_mul_fix(x, x);
> +        x = rb_int_modulo(x, m);
> +    }
> +
> +    tmp = FIX2LONG(tmp2);
> +#endif
> +    if (nega_flg && tmp) {
> +        tmp -= mm;
> +    }
> +    return LONG2FIX(tmp);
> +}
> +
> +static VALUE
> +int_pow_tmp3(VALUE x, VALUE y, VALUE m, int nega_flg)
> +{
> +    VALUE tmp = LONG2FIX(1L);
> +    long yy;
> +
> +    for (/*NOP*/; ! FIXNUM_P(y); y = rb_funcall(y, idGTGT, 1,
> LONG2FIX(1L))) {
> +        if (RTEST(int_odd_p(y))) {
> +            tmp = rb_funcall(tmp, '*', 1, x);
> +            tmp = rb_int_modulo(tmp, m);
> +        }
> +        x = rb_funcall(x, '*', 1, x);
> +        x = rb_int_modulo(x, m);
> +    }
> +    for (yy = FIX2LONG(y); yy; yy >>= 1L) {
> +        if (yy & 1L) {
> +            tmp = rb_funcall(tmp, '*', 1, x);
> +            tmp = rb_int_modulo(tmp, m);
> +        }
> +        x = rb_funcall(x, '*', 1, x);
> +        x = rb_int_modulo(x, m);
> +    }
> +
> +    if (nega_flg && positive_int_p(tmp)) {
> +        tmp = rb_funcall(tmp, '-', 1, m);
> +    }
> +    return tmp;
> +}
> +
> +static VALUE
> +int_pow3(VALUE x, VALUE y, VALUE m)
> +{
> +    int nega_flg = 0;
> +    long mm;
> +
> +    if ( ! (rb_obj_is_kind_of(y, rb_cInteger) && rb_obj_is_kind_of(m,
> rb_cInteger))) {
> +        rb_raise(rb_eTypeError, "Integer#pow() 2nd argument not allowed
> unless all arguments are integers");
> +    }
> +    if (negative_int_p(y)) {
> +        rb_raise(rb_eRangeError, "Integer#pow() 1st argument cannot be
> negative when 2nd argument specified");
> +    }
> +
> +    if (negative_int_p(m)) {
> +        nega_flg = 1;
> +        m = rb_funcall(m, idUMinus, 0);
> +    }
> +
> +    if ( ! positive_int_p(m)) {
> +        rb_num_zerodiv();
> +    }
> +
> +    if (FIXNUM_P(m)) {
> +        mm = FIX2LONG(m);
> +        if (LONG_MAX > 0x7fffffffL) {  // XXX?
> +            if (mm <= 0x80000000L) {
> +                return int_pow_tmp1(rb_int_modulo(x, m), y, mm, nega_flg);
> +            }
> +        } else {
> +            if (mm <= 0x8000L) {
> +                return int_pow_tmp1(rb_int_modulo(x, m), y, mm, nega_flg);
> +            }
> +        }
> +        return int_pow_tmp2(rb_int_modulo(x, m), y, mm, nega_flg);
> +    }
> +    return int_pow_tmp3(rb_int_modulo(x, m), y, m, nega_flg);
> +}
> +
> +/*
> + * call-seq:
> + *   integer.pow(numeric)  ->  integer ** numeric
> + *   integer.pow(a, m)     ->  (integer `pow` a) mod m
> + */
> +
> +static VALUE
> +int_pow2(int argc, VALUE* argv, VALUE x)
> +{
> +    VALUE y, m;
> +
> +    rb_scan_args(argc, argv, "11", &y, &m);
> +
> +    switch (argc) {
> +    case 1:
> +        return rb_funcall(x, rb_intern("**"), 1, y);
> +    case 2:
> +        return int_pow3(x, y, m);
> +    }
> +
> +    return Qnil;  // not reached (XXX?)
> +}
> +
>  /*
>   * call-seq:
>   *   fix > real  ->  true or false
> @@ -4332,6 +4502,7 @@ Init_Numeric(void)
>      rb_define_method(rb_cInteger, "truncate", int_to_i, 0);
>      rb_define_method(rb_cInteger, "round", int_round, -1);
>      rb_define_method(rb_cInteger, "<=>", int_cmp, 1);
> +    rb_define_method(rb_cInteger, "pow", int_pow2, -1);
>
>      rb_cFixnum = rb_define_class("Fixnum", rb_cInteger);
>
>

In This Thread