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

Commit 75ba941

Browse files
author
Stefan Majoor
committed
Added cosine and tangent
1 parent 54fd992 commit 75ba941

File tree

3 files changed

+176
-0
lines changed

3 files changed

+176
-0
lines changed

src/Decimal.php

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,123 @@ public function mod(Decimal $d, $scale = null)
633633
return $this->sub($div->mul($d, $scale));
634634
}
635635

636+
/**
637+
* Calculates the sine of this method with the highest possible accuracy
638+
* Note that accuracy is limited by the accuracy of predefined PI;
639+
*
640+
* @param null $scale
641+
* @return Decimal sin($this)
642+
*/
643+
public function sin($scale = null) {
644+
// First normalise the number in the [0, 2PI] domain
645+
$twoPi = NumConstants::PI()->mul(Decimal::fromString("2"));
646+
$x = $this->mod($twoPi);
647+
$zero = Decimal::fromString("0");
648+
649+
// PI has only 32 siginficant numbers
650+
$significantNumbers = is_null($scale) ? 32 : $scale;
651+
652+
// Next use Maclaurin's theorem to approximate sin with high enough accuracy
653+
// note that the accuracy is depended on the accuracy of the given PI constant
654+
$faculty = Decimal::fromString("1"); // Calculates the faculty under the sign
655+
$loopCounter = 1; // Calculates the iteration we are in
656+
$xPowerN = Decimal::fromString("1"); // Calculates x^n
657+
$approx = Decimal::fromString("0"); // keeps track of our approximation for sin(x)
658+
659+
while (true) {
660+
// update x^n and n! for this walkthrough
661+
$xPowerN = $xPowerN->mul($x);
662+
$faculty = $faculty->mul(Decimal::fromString((string) $loopCounter));
663+
664+
// only do calculations if n is uneven
665+
// otherwise result is zero anyways
666+
if ($loopCounter % 2 === 1) {
667+
// calculate the absolute change in this iteration.
668+
$change = $xPowerN->div($faculty);
669+
670+
// change should be added if x mod 4 == 1 and subtracted if x mod 4 == 3
671+
if ($loopCounter % 4 === 1) {
672+
$approx = $approx->add($change);
673+
} else {
674+
$approx = $approx->sub($change);
675+
}
676+
677+
// Terminate the method if our change is sufficiently small
678+
if ($change->floor($significantNumbers)->equals($zero)) {
679+
return $approx->round($significantNumbers);
680+
}
681+
}
682+
683+
684+
$loopCounter++;
685+
}
686+
}
687+
688+
/**
689+
* Calculates the cosine of this method with the highest possible accuracy
690+
* Note that accuracy is limited by the accuracy of predefined PI;
691+
*
692+
* @param null $scale
693+
* @return Decimal cos($this)
694+
*/
695+
public function cos($scale = null) {
696+
// First normalise the number in the [0, 2PI] domain
697+
$twoPi = NumConstants::PI()->mul(Decimal::fromString("2"));
698+
$x = $this->mod($twoPi);
699+
$zero = Decimal::fromString("0");
700+
701+
// PI has only 32 siginficant numbers
702+
$significantNumbers = is_null($scale) ? 32 : $scale;
703+
704+
// Next use Maclaurin's theorem to approximate sin with high enough accuracy
705+
// note that the accuracy is depended on the accuracy of the given PI constant
706+
$faculty = Decimal::fromString("1"); // Calculates the faculty under the sign
707+
$loopCounter = 1; // Calculates the iteration we are in
708+
$xPowerN = Decimal::fromString("1"); // Calculates x^n
709+
$approx = Decimal::fromString("1"); // keeps track of our approximation for sin(x)
710+
711+
while (true) {
712+
// update x^n and n! for this walkthrough
713+
$xPowerN = $xPowerN->mul($x);
714+
$faculty = $faculty->mul(Decimal::fromString((string) $loopCounter));
715+
716+
// only do calculations if n is uneven
717+
// otherwise result is zero anyways
718+
if ($loopCounter % 2 === 0) {
719+
// calculate the absolute change in this iteration.
720+
$change = $xPowerN->div($faculty);
721+
722+
// change should be added if x mod 4 == 1 and subtracted if x mod 4 == 3
723+
if ($loopCounter % 4 === 0) {
724+
$approx = $approx->add($change);
725+
} else {
726+
$approx = $approx->sub($change);
727+
}
728+
729+
// Terminate the method if our change is sufficiently small
730+
if ($change->floor($significantNumbers )->equals($zero)) {
731+
return $approx->round($significantNumbers);
732+
}
733+
}
734+
735+
736+
$loopCounter++;
737+
}
738+
}
739+
740+
/**
741+
* Calculates the tangent of this method with the highest possible accuracy
742+
* Note that accuracy is limited by the accuracy of predefined PI;
743+
*
744+
* @param null $scale
745+
* @return Decimal tan($this)
746+
*/
747+
public function tan($scale = null) {
748+
return $this->sin($scale + 2)
749+
->div($this->cos($scale + 2))
750+
->floor($scale);
751+
}
752+
636753
public function hasSameSign(Decimal $b) {
637754
return $this->isPositive() && $b->isPositive() || $this->isNegative() && $b->isNegative();
638755
}

tests/Decimal/DecimalCosTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
use Litipk\BigNumbers\Decimal as Decimal;
4+
5+
/**
6+
* @group cos
7+
*/
8+
class DecimalCosTest extends PHPUnit_Framework_TestCase
9+
{
10+
public function cosProvider() {
11+
// Some values providede by mathematica
12+
return array(
13+
array('1', '0.54030230586814', '14'),
14+
array('123.123', '-0.82483472946164834', '17'),
15+
array('15000000000', '-0.72218064388924347681', '20')
16+
17+
);
18+
}
19+
20+
/**
21+
* @dataProvider cosProvider
22+
*/
23+
public function testSimple($nr, $answer, $digits)
24+
{
25+
$x = Decimal::fromString($nr);
26+
$cosX = $x->cos($digits);
27+
28+
$this->assertTrue(Decimal::fromString($answer)->equals($cosX));
29+
}
30+
}

tests/Decimal/DecimalTanTest.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
use Litipk\BigNumbers\Decimal as Decimal;
4+
5+
/**
6+
* @group tan
7+
*/
8+
class DecimalTanTest extends PHPUnit_Framework_TestCase
9+
{
10+
public function tanProvider() {
11+
// Some values providede by mathematica
12+
return array(
13+
array('1', '1.55740772465490', '14'),
14+
array('123.123', '0.68543903342472368', '17'),
15+
array('15000000000', '-0.95779983511717825557', '20')
16+
17+
);
18+
}
19+
20+
/**
21+
* @dataProvider tanProvider
22+
*/
23+
public function testSimple($nr, $answer, $digits)
24+
{
25+
$x = Decimal::fromString($nr);
26+
$tanX = $x->tan($digits);
27+
$this->assertTrue(Decimal::fromString($answer)->equals($tanX));
28+
}
29+
}

0 commit comments

Comments
 (0)