/*
 *  call-seq:
 *     big ** exponent   => numeric
 *
 *  Raises _big_ to the _exponent_ power (which may be an integer, float,
 *  or anything that will coerce to a number). The result may be
 *  a Fixnum, Bignum, or Float
 *
 *    123456789 ** 2      #=> 15241578750190521
 *    123456789 ** 1.2    #=> 5126464716.09932
 *    123456789 ** -2     #=> 6.5610001194102e-17
 */

VALUE
rb_big_pow(VALUE x, VALUE y)
{
    double d;
    SIGNED_VALUE yy;

    if (y == INT2FIX(0)) return INT2FIX(1);
    switch (TYPE(y)) {
      case T_FLOAT:
        d = RFLOAT_VALUE(y);
        break;

      case T_BIGNUM:
        if (rb_funcall(y, '<', 1, INT2FIX(0)))
          return rb_funcall(rb_rational_raw1(x), rb_intern("**"), 1, y);

        rb_warn("in a**b, b may be too big");
        d = rb_big2dbl(y);
        break;

      case T_FIXNUM:
        yy = FIX2LONG(y);

        if (yy < 0)
          return rb_funcall(rb_rational_raw1(x), rb_intern("**"), 1, y);
        else {
            VALUE z = 0;
            SIGNED_VALUE mask;
            const long BIGLEN_LIMIT = 1024*1024 / SIZEOF_BDIGITS;

            if ((RBIGNUM_LEN(x) > BIGLEN_LIMIT) ||
                (RBIGNUM_LEN(x) > BIGLEN_LIMIT / yy)) {
                rb_warn("in a**b, b may be too big");
                d = (double)yy;
                break;
            }
            for (mask = FIXNUM_MAX + 1; mask; mask >>= 1) {
                if (z) z = bigtrunc(bigsqr(z));
                if (yy & mask) {
                    z = z ? bigtrunc(rb_big_mul0(z, x)) : x;
                }
            }
            return bignorm(z);
        }
        /* NOTREACHED */
        break;

      default:
        return rb_num_coerce_bin(x, y, rb_intern("**"));
    }
    return DOUBLE2NUM(pow(rb_big2dbl(x), d));
}