diff --git a/phpseclib/Math/BigInteger.php b/phpseclib/Math/BigInteger.php index b4513b6e..abc6ef0c 100644 --- a/phpseclib/Math/BigInteger.php +++ b/phpseclib/Math/BigInteger.php @@ -3699,13 +3699,17 @@ class BigInteger */ function root($n = null) { - $one = new static(1); - $two = new static(2); + static $zero, $one, $two; + if (!isset($one)) { + $zero = new static(0); + $one = new static(1); + $two = new static(2); + } if ($n === null) { $n = $two; } if ($n->compare($one) == -1) { - return new static(0); + return $zero; } // we want positive exponents if ($this->compare($one) == -1) { return new static(0); @@ -3713,99 +3717,64 @@ class BigInteger if ($this->compare($two) == -1) { return $one; } // n-th root of 1 or 2 is 1 + $root = new static(); - switch (MATH_BIGINTEGER_MODE) { - case self::MODE_BCMATH: - // g is our guess number - $g = 2; - // while (g^n < num) g=g*2 - while (bccomp(bcpow($g, $n->value), $this->value) == -1) { - $g = bcmul($g, '2'); - } - // if (g^n==num) num is a power of 2, we're lucky, end of job - if (bccomp(bcpow($g, $n->value), $this->value) == 0) { - $root->value = $g; - break; - } - - // if we're here num wasn't a power of 2 :( - $og = $g; // og means original guess and here is our upper bound - $g = bcdiv($g, '2'); // g is set to be our lower bound - $step = bcdiv(bcsub($og, $g), '2'); // step is the half of upper bound - lower bound - $g = bcadd($g, $step); // we start at lower bound + step , basically in the middle of our interval - - // while step!=1 - - while (bccomp($step, '1') == 1) { - $guess = bcpow($g, $n); - $step = bcdiv($step, '2'); - $comp = bccomp($guess, $this->value); // compare our guess with real number - if ($comp == -1) { // if guess is lower we add the new step - $g = bcadd($g, $step); - } elseif ($comp == 1) { // if guess is higher we sub the new step - $g = bcsub($g, $step); - } else { // if guess is exactly the num we're done, we return the value - $root->value = $g; - break; - } - } - - // whatever happened, g is the closest guess we can make so return it - $root->value = $g; - break; - case self::MODE_GMP: - if (function_exists('gmp_root')) { - $root->value = gmp_root($this->value, gmp_intval($n->value)); - break; - } - default: - // g is our guess number - $g = $two; - // while (g^n < num) g=g*2 - while ($g->pow($n)->compare($this) == -1) { - $g = $g->multiply($two); - } - // if (g^n==num) num is a power of 2, we're lucky, end of job - // == 0 bccomp(bcpow($g,$n), $n->value)==0 - if ($g->pow($n)->equals($this)) { - $root = $g; - break; - } - - // if we're here num wasn't a power of 2 :( - $og = $g; // og means original guess and here is our upper bound - $g = $g->divide($two)[0]; // g is set to be our lower bound - $step = $og->subtract($g)->divide($two)[0]; // step is the half of upper bound - lower bound - $g = $g->add($step); // we start at lower bound + step , basically in the middle of our interval - - // while step!=1 - - while ($step->compare($one) == 1) { - $guess = $g->pow($n); - $step = $step->divide($two)[0]; - $comp = $guess->compare($this); // compare our guess with real number - if ($comp == -1) { // if guess is lower we add the new step - $g = $g->add($step); - } elseif ($comp == 1) { // if guess is higher we sub the new step - $g = $g->subtract($step); - } else { // if guess is exactly the num we're done, we return the value - $root = $g; - break; - } - } - - // whatever happened, g is the closest guess we can make so return it - $root = $g; - break; + if (MATH_BIGINTEGER_MODE == self::MODE_GMP && function_exists('gmp_root')) { + $root->value = gmp_root($this->value, gmp_intval($n->value)); + return $this->_normalize($root); } + // g is our guess number + $g = $two; + // while (g^n < num) g=g*2 + while ($g->pow($n)->compare($this) == -1) { + $g = $g->multiply($two); + } + // if (g^n==num) num is a power of 2, we're lucky, end of job + // == 0 bccomp(bcpow($g,$n), $n->value)==0 + if ($g->pow($n)->equals($this)) { + $root = $g; + return $this->_normalize($root); + } + + // if we're here num wasn't a power of 2 :( + $og = $g; // og means original guess and here is our upper bound + $g = $g->divide($two)[0]; // g is set to be our lower bound + $step = $og->subtract($g)->divide($two)[0]; // step is the half of upper bound - lower bound + $g = $g->add($step); // we start at lower bound + step , basically in the middle of our interval + + // while step>1 + + while ($step->compare($one) == 1) { + $guess = $g->pow($n); + $step = $step->divide($two)[0]; + $comp = $guess->compare($this); // compare our guess with real number + switch ($comp) { + case -1: // if guess is lower we add the new step + $g = $g->add($step); + break; + case 1: // if guess is higher we sub the new step + $g = $g->subtract($step); + break; + case 0: // if guess is exactly the num we're done, we return the value + $root = $g; + break 2; + } + } + + if ($comp == 1) { + $g = $g->subtract($step); + } + + // whatever happened, g is the closest guess we can make so return it + $root = $g; + return $this->_normalize($root); } /** * Performs exponentiation. * - * * @param \phpseclib\Math\BigInteger $n * @access public * @return \phpseclib\Math\BigInteger @@ -3836,73 +3805,78 @@ class BigInteger } return $res; - break; } } /** - * Return the minimum BigInteger between two BigIntegers. + * Return the minimum BigInteger between an arbitrary number of BigIntegers. * - * - * @param \phpseclib\Math\BigInteger $a - * @param \phpseclib\Math\BigInteger $b + * @param \phpseclib\Math\BigInteger ...$param * @access public - * - * * @return \phpseclib\Math\BigInteger */ - function min($b) + static function min() { - if ($this->compare($b) == '1') { - return $b; + $args = func_get_args(); + if (count($args) == 1) { + return $args[0]; } - - return $this; + $min = $args[0]; + for ($i = 1; $i < count($args); $i++) { + $min = $min->compare($args[$i]) > 0 ? $args[$i] : $min; + } + return $min; } /** - * Return the maximum BigInteger between two BigIntegers. + * Return the maximum BigInteger between an arbitrary number of BigIntegers. * - * - * @param \phpseclib\Math\BigInteger $b + * @param \phpseclib\Math\BigInteger ...$param * @access public - * * @return \phpseclib\Math\BigInteger */ - function max($b) + static function max() { - if ($this->compare($b) == '1') { - return $this; + $args = func_get_args(); + if (count($args) == 1) { + return $args[0]; } - - return $b; + $max = $args[0]; + for ($i = 1; $i < count($args); $i++) { + $max = $max->compare($args[$i]) < 0 ? $args[$i] : $max; + } + return $max; } /** * Execute a function n times, where n is the current BigInteger. - * The passed function must accept a paremeter that will be set to the the current number + * The passed function must accept a parameter that will be set to the the current number * (that number will range from zero to BigInteger - 1 if the BigInteger is positive, and from BigInteger - 1 to zero if the BigInteger is negative). * You can also set it to accept an optional parameter, that will be equal to the optional $userdata parameter. * * @access public * @param callable $function * @param &$userdata - * * @return \phpseclib\Math\BigInteger */ function loopforeach(callable $function, &$userdata = null) { - if ($this->compare(new static(0)) < 0) { // negative + static $zero; + if (!isset($zero)) { + $zero = new static(0); + } + if ($this->compare($zero) < 0) { // negative $limit = new static(-PHP_INT_MAX - 1); $one = new static(-1); } else { // positive $limit = new static(PHP_INT_MAX); $one = new static(1); } + if ($this->compare($limit) == -((int) $one->toString())) { $oneint = (int) $one->toString(); $thisint = (int) $this->toString(); - for ($loop = 0; $loop != $thisint; $loop += $oneint) { + for ($loop = 0; $loop != $thisint; $loop+= $oneint) { $function($loop, $userdata); } } else { diff --git a/tests/Unit/Math/BigInteger/TestCase.php b/tests/Unit/Math/BigInteger/TestCase.php index 37a7191e..c4a51eac 100644 --- a/tests/Unit/Math/BigInteger/TestCase.php +++ b/tests/Unit/Math/BigInteger/TestCase.php @@ -405,16 +405,16 @@ abstract class Unit_Math_BigInteger_TestCase extends PhpseclibTestCase { $min = new \phpseclib\Math\BigInteger('20'); $max = new \phpseclib\Math\BigInteger('20000'); - $this->assertSame((string) $max, (string) $min->max($max)); - $this->assertSame((string) $max, (string) $max->max($min)); + $this->assertSame((string) $max, (string) \phpseclib\Math\BigInteger::max($min, $max)); + $this->assertSame((string) $max, (string) \phpseclib\Math\BigInteger::max($max, $min)); } public function testMin() { $min = new \phpseclib\Math\BigInteger('20'); $max = new \phpseclib\Math\BigInteger('20000'); - $this->assertSame((string) $min, (string) $min->min($max)); - $this->assertSame((string) $min, (string) $max->min($min)); + $this->assertSame((string) $min, (string) \phpseclib\Math\BigInteger::min($min, $max)); + $this->assertSame((string) $min, (string) \phpseclib\Math\BigInteger::min($max, $min)); } public function testLoopForeach()