Skip to content
This repository was archived by the owner on Dec 27, 2023. It is now read-only.

Commit 7f84384

Browse files
committed
Added the pow method to the Decimal class. Fixed a bug in the Decimal::isPositive method
1 parent 72ee4e5 commit 7f84384

File tree

2 files changed

+176
-3
lines changed

2 files changed

+176
-3
lines changed

src/Litipk/BigNumbers/Decimal.php

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ public function div (BigNumber $b, $scale = null)
369369

370370
/**
371371
* Returns the square root of this object
372-
*
372+
* @param integer $scale
373373
* @return Decimal
374374
*/
375375
public function sqrt ($scale = null)
@@ -384,10 +384,56 @@ public function sqrt ($scale = null)
384384

385385
return self::fromString(
386386
bcsqrt($this->value, $sqrt_scale+1),
387-
$scale
387+
$sqrt_scale
388388
);
389389
}
390390

391+
/**
392+
* Powers this value to $b
393+
*
394+
* @param Decimal $b exponent
395+
* @param integer $scale
396+
* @return Decimal
397+
*/
398+
public function pow(Decimal $b, $scale = null)
399+
{
400+
if ($this->isZero()) {
401+
if ($b->isPositive()) {
402+
return Decimal::fromDecimal($this, $scale);
403+
} else {
404+
return NaN::getNaN();
405+
}
406+
} elseif($b->isZero()) {
407+
return Decimal::fromInteger(1, $scale);
408+
} elseif ($b->scale == 0) {
409+
$pow_scale = $scale === null ?
410+
max($this->scale, $b->scale) : max($this->scale, $b->scale, $scale);
411+
412+
return self::fromString(
413+
bcpow($this->value, $b->value, $pow_scale+1),
414+
$pow_scale
415+
);
416+
} else {
417+
if ($this->isPositive()) {
418+
$pow_scale = $scale === null ?
419+
max($this->scale, $b->scale) : max($this->scale, $b->scale, $scale);
420+
421+
$truncated_b = bcadd($b->value, '0', 0);
422+
$remaining_b = bcsub($b->value, $truncated_b, $b->scale);
423+
424+
$first_pow_approx = bcpow($this->value, $truncated_b, $pow_scale+1);
425+
$intermediate_root = self::innerPowWithLittleExponent($this->value, $remaining_b, $b->scale, $pow_scale+1);
426+
427+
return Decimal::fromString(
428+
bcmul($first_pow_approx, $intermediate_root, $pow_scale+1),
429+
$pow_scale
430+
);
431+
} else { // elseif ($this->isNegative())
432+
return NaN::getNaN();
433+
}
434+
}
435+
}
436+
391437
/**
392438
* Returns the object's logarithm in base 10
393439
* @param integer $scale
@@ -422,7 +468,7 @@ public function isZero ($scale = null)
422468
*/
423469
public function isPositive ()
424470
{
425-
return ($this->value[0] !== '-');
471+
return ($this->value[0] !== '-' && !$this->isZero());
426472
}
427473

428474
/**
@@ -647,4 +693,52 @@ private static function innerLog10 ($value, $in_scale, $out_scale)
647693
return '0';
648694
}
649695
}
696+
697+
698+
private static function innerPowWithLittleExponent ($base, $exponent, $exp_scale, $out_scale)
699+
{
700+
$inner_scale = ceil($exp_scale*log(10)/log(2))+1;
701+
702+
$result_a = '1';
703+
$result_b = '0';
704+
705+
$actual_index = 0;
706+
$exponent_remaining = $exponent;
707+
708+
while (bccomp($result_a, $result_b, $out_scale) !== 0 && bccomp($exponent_remaining, '0', $inner_scale) !== 0) {
709+
$result_b = $result_a;
710+
$index_info = self::computeSquareIndex($exponent_remaining, $actual_index, $exp_scale, $inner_scale);
711+
$exponent_remaining = $index_info[1];
712+
$result_a = bcmul($result_a, self::compute2NRoot($base, $index_info[0], 2*($out_scale+1)), 2*($out_scale+1));
713+
}
714+
715+
return self::innerRound($result_a, $out_scale);
716+
}
717+
718+
719+
private static function computeSquareIndex ($exponent_remaining, $actual_index, $exp_scale, $inner_scale)
720+
{
721+
$actual_rt = bcpow('0.5', $actual_index, $exp_scale);
722+
$r = bcsub($exponent_remaining, $actual_rt, $inner_scale);
723+
724+
while (bccomp($r, 0, $exp_scale) === -1) {
725+
++$actual_index;
726+
$actual_rt = bcmul('0.5', $actual_rt, $inner_scale);
727+
$r = bcsub($exponent_remaining, $actual_rt, $inner_scale);
728+
}
729+
730+
return [$actual_index, $r];
731+
}
732+
733+
734+
private static function compute2NRoot ($base, $index, $out_scale)
735+
{
736+
$result = $base;
737+
738+
for ($i=0; $i<$index; $i++) {
739+
$result = bcsqrt($result, ($out_scale+1)*($index-$i)+1);
740+
}
741+
742+
return self::innerRound($result, $out_scale);
743+
}
650744
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
use Litipk\BigNumbers\Decimal as Decimal;
4+
5+
class DecimalPowTest extends PHPUnit_Framework_TestCase
6+
{
7+
public function testZeroPositive ()
8+
{
9+
$zero = Decimal::fromInteger(0);
10+
$two = Decimal::fromInteger(2);
11+
12+
$this->assertTrue($zero->pow($two)->isZero());
13+
}
14+
15+
public function testZeroNoPositive ()
16+
{
17+
$zero = Decimal::fromInteger(0);
18+
$nTwo = Decimal::fromInteger(-2);
19+
20+
$this->assertTrue($zero->pow($nTwo)->isNaN());
21+
$this->assertTrue($zero->pow($zero)->isNaN());
22+
}
23+
24+
public function testNoZeroZero ()
25+
{
26+
$zero = Decimal::fromInteger(0);
27+
$one = Decimal::fromInteger(1);
28+
29+
$nTwo = Decimal::fromInteger(-2);
30+
$pTwo = Decimal::fromInteger(2);
31+
32+
$this->assertTrue($nTwo->pow($zero)->equals($one));
33+
$this->assertTrue($pTwo->pow($zero)->equals($one));
34+
}
35+
36+
public function testLittleIntegerInteger ()
37+
{
38+
$two = Decimal::fromInteger(2);
39+
$three = Decimal::fromInteger(3);
40+
$four = Decimal::fromInteger(4);
41+
$eight = Decimal::fromInteger(8);
42+
$nine = Decimal::fromInteger(9);
43+
$twentyseven = Decimal::fromInteger(27);
44+
45+
$this->assertTrue($two->pow($two)->equals($four));
46+
$this->assertTrue($two->pow($three)->equals($eight));
47+
48+
$this->assertTrue($three->pow($two)->equals($nine));
49+
$this->assertTrue($three->pow($three)->equals($twentyseven));
50+
}
51+
52+
public function testLittlePositiveSquareRoot ()
53+
{
54+
$half = Decimal::fromString('0.5');
55+
$two = Decimal::fromInteger(2);
56+
$three = Decimal::fromInteger(3);
57+
$four = Decimal::fromInteger(4);
58+
$nine = Decimal::fromInteger(9);
59+
60+
$this->assertTrue($four->pow($half)->equals($two));
61+
$this->assertTrue($nine->pow($half)->equals($three));
62+
}
63+
64+
public function testBigPositiveSquareRoot ()
65+
{
66+
$half = Decimal::fromString('0.5');
67+
$bignum1 = Decimal::fromString('922337203685477580700');
68+
69+
$this->assertTrue($bignum1->pow($half, 6)->equals($bignum1->sqrt(6)));
70+
}
71+
72+
public function testNegativeSquareRoot ()
73+
{
74+
$half = Decimal::fromString('0.5');
75+
$nThree = Decimal::fromInteger(-3);
76+
77+
$this->assertTrue($nThree->pow($half)->isNaN());
78+
}
79+
}

0 commit comments

Comments
 (0)