|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
|
|
4
|
|
|
namespace Samsara\Fermat\Provider; |
|
5
|
|
|
|
|
6
|
|
|
|
|
7
|
|
|
use Samsara\Exceptions\UsageError\IntegrityConstraint; |
|
8
|
|
|
use Samsara\Fermat\Numbers; |
|
9
|
|
|
use Samsara\Fermat\Types\Base\Selectable; |
|
10
|
|
|
|
|
11
|
|
|
class ConstantProvider |
|
12
|
|
|
{ |
|
13
|
|
|
|
|
14
|
3 |
|
public static function makePi(int $digits): string |
|
15
|
|
|
{ |
|
16
|
|
|
|
|
17
|
3 |
|
$internalPrecision = $digits + 10; |
|
18
|
|
|
|
|
19
|
3 |
|
$C = Numbers::make(Numbers::IMMUTABLE, '10005', $internalPrecision)->sqrt($internalPrecision)->multiply(426880); |
|
20
|
3 |
|
$M = Numbers::make(Numbers::IMMUTABLE, '1', $internalPrecision); |
|
21
|
3 |
|
$L = Numbers::make(Numbers::IMMUTABLE, '13591409', $internalPrecision); |
|
22
|
3 |
|
$K = Numbers::make(Numbers::IMMUTABLE, '6', $internalPrecision); |
|
23
|
3 |
|
$X = Numbers::make(Numbers::IMMUTABLE, '1'); |
|
24
|
3 |
|
$sum = Numbers::make(Numbers::MUTABLE,'0', $internalPrecision + 2); |
|
25
|
3 |
|
$termNum = 0; |
|
26
|
3 |
|
$one = Numbers::makeOne($internalPrecision); |
|
27
|
|
|
|
|
28
|
3 |
|
$continue = true; |
|
29
|
|
|
|
|
30
|
3 |
|
while ($continue) { |
|
31
|
3 |
|
$term = $M->multiply($L)->divide($X); |
|
32
|
|
|
|
|
33
|
3 |
|
if ($term->numberOfLeadingZeros() > $internalPrecision || $term->isEqual(0)) { |
|
34
|
3 |
|
$continue = false; |
|
35
|
|
|
} |
|
36
|
|
|
|
|
37
|
3 |
|
$sum->add($term); |
|
38
|
|
|
|
|
39
|
3 |
|
$M = $M->multiply($K->pow(3)->subtract($K->multiply(16))->divide($one->add($termNum)->pow(3), $internalPrecision)); |
|
40
|
3 |
|
$L = $L->add(545140134); |
|
41
|
3 |
|
$X = $X->multiply('-262537412640768000'); |
|
42
|
3 |
|
$K = $K->add(12); |
|
43
|
3 |
|
$termNum++; |
|
44
|
|
|
} |
|
45
|
|
|
|
|
46
|
3 |
|
$pi = $C->divide($sum, $internalPrecision); |
|
47
|
|
|
|
|
48
|
3 |
|
return $pi->truncateToPrecision($digits)->getValue(); |
|
49
|
|
|
|
|
50
|
|
|
} |
|
51
|
|
|
|
|
52
|
|
|
/** |
|
53
|
|
|
* Consider also: sum [0 -> INF] { (2n + 2) / (2n + 1)! } |
|
54
|
|
|
* |
|
55
|
|
|
* This converges faster (though it's unclear if the calculation is actually faster), and can be represented by this |
|
56
|
|
|
* set of Fermat calls: |
|
57
|
|
|
* |
|
58
|
|
|
* SequenceProvider::nthEvenNumber($n + 1)->divide(SequenceProvider::nthOddNumber($n)->factorial()); |
|
59
|
|
|
* |
|
60
|
|
|
* Perhaps by substituting the nthOddNumber()->factorial() call with something tracked locally, the performance can |
|
61
|
|
|
* be improved. Current performance is acceptable even out past 200 digits. |
|
62
|
|
|
* |
|
63
|
|
|
* @param int $digits |
|
64
|
|
|
* @return string |
|
65
|
|
|
* @throws IntegrityConstraint |
|
66
|
|
|
*/ |
|
67
|
2 |
|
public static function makeE(int $digits): string |
|
68
|
|
|
{ |
|
69
|
|
|
|
|
70
|
2 |
|
$internalPrecision = $digits + 3; |
|
71
|
|
|
|
|
72
|
2 |
|
$one = Numbers::makeOne($internalPrecision+5)->setMode(Selectable::CALC_MODE_PRECISION); |
|
73
|
2 |
|
$denominator = Numbers::make(Numbers::MUTABLE, '1', $internalPrecision)->setMode(Selectable::CALC_MODE_PRECISION); |
|
74
|
2 |
|
$e = Numbers::make(NUmbers::MUTABLE, '2', $internalPrecision)->setMode(Selectable::CALC_MODE_PRECISION); |
|
75
|
2 |
|
$n = Numbers::make(Numbers::MUTABLE, '2', $internalPrecision)->setMode(Selectable::CALC_MODE_PRECISION); |
|
76
|
|
|
|
|
77
|
2 |
|
$continue = true; |
|
78
|
|
|
|
|
79
|
2 |
|
while ($continue) { |
|
80
|
2 |
|
$denominator->multiply($n); |
|
81
|
2 |
|
$n->add($one); |
|
82
|
2 |
|
$term = $one->divide($denominator); |
|
83
|
|
|
|
|
84
|
2 |
|
if ($term->numberOfLeadingZeros() > $internalPrecision || $term->isEqual(0)) { |
|
85
|
2 |
|
$continue = false; |
|
86
|
|
|
} |
|
87
|
|
|
|
|
88
|
2 |
|
$e->add($term); |
|
89
|
|
|
} |
|
90
|
|
|
|
|
91
|
2 |
|
return $e->truncateToPrecision($digits)->getValue(); |
|
92
|
|
|
|
|
93
|
|
|
} |
|
94
|
|
|
|
|
95
|
|
|
} |