/* {{{ proto number pow(number base, number exponent)
   Returns base raised to the power of exponent. Returns
   integer result when possible. */

PHP_FUNCTION(pow)
{
	zval **zbase, **zexp;
	/* I'm not allowed to allocate it lower down :-( */
	long base,exp;
	
	if (ZEND_NUM_ARGS() != 2) {
		WRONG_PARAM_COUNT;
	} 
	/* can't fail if num args is correct */
	zend_get_parameters_ex(2,&zbase,&zexp);
	convert_scalar_to_number_ex(zbase);
	convert_scalar_to_number_ex(zexp);


	/* If exp != int, we should do floating pow, using c-func pow() */
	if ( Z_TYPE_PP(zexp) != IS_LONG) {
		double dbase;
		dbase = Z_TYPE_PP(zbase) == IS_LONG ? (double) Z_LVAL_PP(zbase)
											:          Z_DVAL_PP(zbase);
		if ( dbase <= 0.0 ) {
			/* Note that with the old behaviour, php pow() returned bogus
			   results. Try pow(-1,2.5) in PHP <= 4.0.6 ... */
			php_error(E_WARNING,"Trying to raise a nonpositive value to a broken power");
			/* There is *really* nothing logical that can be done */
			RETURN_FALSE;
		}

		/* Value's are okay, let's use pow()
		   pow() should be used as exp( log(base) * exp ) now... 
		   FIXME: maybe simply use that? pow() might be doing
		   strange things, and we've now already tracked down the problem
		   so that we know which formula to use... */
		RETURN_DOUBLE(pow(dbase, Z_DVAL_PP(zexp)));
	}

	/* We have integer exponent, meaning that pow is always possible */

	/* If base if float, result should be float, and C-pow() works fine */
	if (Z_TYPE_PP(zbase) == IS_DOUBLE) {
		RETURN_DOUBLE(pow(Z_DVAL_PP(zbase), Z_DVAL_PP(zexp)));
	}
	
	/* pow(int,int) */

	/* will be needed anyway later, so it's better to define them now already */
	base = Z_LVAL_PP(zbase);
	exp  = Z_LVAL_PP(zexp);

	if (!exp) {
		RETURN_LONG(1); /* FIXME (important): should it be 1L here? */
	}

	if (base == -1) {
		RETURN_LONG( exp & 1 ? -1 : 1 ); /* if exp=odd ... */
	} else if (base == 0) {
		if (exp < 0) {
			php_error(E_WARNING,"Division by zero (pow(0,-1))");
			RETURN_FALSE;
		} else {
			RETURN_LONG(0);
		}
	} else if (base == 1) {
		RETURN_LONG(1);
	} else {
		/* abs(base) > 1 */
		double dval = pow( (double) base, (double) exp);
		long result = 1; /* already here... same issue */
		if (exp < 0 || dval > (double) LONG_MAX ) {
			/* 1/n ( abs(n) > 1 ) || overflow */
			RETURN_DOUBLE(dval);
		}
		/* FIXME: result could be so large that dval is bogus,
		   and you still come here. The modus of the returned
		   integer will be valid though :), even in decent time */
		/* Fasted algorithm there is, runs in O(logn), where n=exp */
		/* long result = 1; <-- not allowed */
		while (exp > 0) {
			if (exp & 1) /* odd */
				result *= base;
			exp >>= 1;
			base *= base;
		}
		RETURN_LONG(result);
	}
}

/* }}} */

