ConstantProvider::makeLn1p1()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
dl 0
loc 16
ccs 10
cts 10
cp 1
crap 3
rs 9.9666
c 0
b 0
f 0
eloc 9
nc 2
nop 1
1
<?php
2
3
4
namespace Samsara\Fermat\Core\Provider;
5
6
7
use Samsara\Exceptions\SystemError\PlatformError\MissingPackage;
8
use Samsara\Exceptions\UsageError\IntegrityConstraint;
9
use Samsara\Fermat\Core\Enums\CalcMode;
10
use Samsara\Fermat\Core\Enums\NumberBase;
11
use Samsara\Fermat\Core\Numbers;
12
use Samsara\Fermat\Core\Types\Base\Interfaces\Numbers\DecimalInterface;
13
use Samsara\Fermat\Core\Values\ImmutableDecimal;
14
15
/**
16
 *
17
 */
18
class ConstantProvider
19
{
20
21
    private static DecimalInterface $pi;
22
    private static DecimalInterface $e;
23
    private static DecimalInterface $ln10;
24
    private static DecimalInterface $ln2;
25
    private static DecimalInterface $ln1p1;
26
    private static DecimalInterface $phi;
27
    private static DecimalInterface $ipowi;
28
29
    /**
30
     * @param int $digits
31
     * @return string
32
     * @throws IntegrityConstraint
33
     * @throws MissingPackage
34
     */
35 12
    public static function makePi(int $digits): string
36
    {
37
38 12
        if (isset(self::$pi) && self::$pi->numberOfDecimalDigits() >= $digits) {
39 8
            return self::$pi->truncateToScale($digits)->getValue(NumberBase::Ten);
40
        }
41
42 6
        $internalScale = ($digits*2) + 10;
43
44 6
        $C = Numbers::make(Numbers::IMMUTABLE, '10005', $internalScale)->setMode(CalcMode::Precision)->sqrt($internalScale)->multiply(426880);
45 6
        $M = Numbers::make(Numbers::IMMUTABLE, '1', $internalScale)->setMode(CalcMode::Precision);
46 6
        $L = Numbers::make(Numbers::IMMUTABLE, '13591409', $internalScale)->setMode(CalcMode::Precision);
47 6
        $K = Numbers::make(Numbers::IMMUTABLE, '6', $internalScale)->setMode(CalcMode::Precision);
48 6
        $X = Numbers::make(Numbers::IMMUTABLE, '1')->setMode(CalcMode::Precision);
49 6
        $sum = Numbers::make(Numbers::MUTABLE,'0', $internalScale + 2)->setMode(CalcMode::Precision);
50 6
        $termNum = 0;
51 6
        $one = Numbers::makeOne($internalScale)->setMode(CalcMode::Precision);
52
53 6
        $continue = true;
54
55 6
        while ($continue) {
56 6
            $term = $M->multiply($L)->divide($X, $internalScale);
57
58 6
            if ($termNum > $internalScale) {
59 6
                $continue = false;
60
            }
61
62 6
            $sum->add($term);
63
64 6
            $M = $M->multiply($K->pow(3)->subtract($K->multiply(16))->divide($one->add($termNum)->pow(3), $internalScale));
65 6
            $L = $L->add(545140134);
66 6
            $X = $X->multiply('-262537412640768000');
67 6
            $K = $K->add(12);
68 6
            $termNum++;
69
        }
70
71 6
        $pi = $C->divide($sum, $internalScale);
72
73 6
        self::$pi = $pi->truncateToScale($digits);
74
75 6
        return $pi->truncateToScale($digits)->getValue(NumberBase::Ten);
76
77
    }
78
79
    /**
80
     * Consider also: sum [0 -> INF] { (2n + 2) / (2n + 1)! }
81
     *
82
     * This converges faster (though it's unclear if the calculation is actually faster), and can be represented by this
83
     * set of Fermat calls:
84
     *
85
     * SequenceProvider::nthEvenNumber($n + 1)->divide(SequenceProvider::nthOddNumber($n)->factorial());
86
     *
87
     * Perhaps by substituting the nthOddNumber()->factorial() call with something tracked locally, the performance can
88
     * be improved. Current performance is acceptable even out past 200 digits.
89
     *
90
     * @param int $digits
91
     * @return string
92
     * @throws IntegrityConstraint
93
     */
94 4
    public static function makeE(int $digits): string
95
    {
96
97 4
        if (isset(self::$e) && self::$e->numberOfDecimalDigits() >= $digits) {
98 2
            return self::$e->truncateToScale($digits)->getValue(NumberBase::Ten);
99
        }
100
101 2
        $internalScale = $digits + 3;
102
103 2
        $one = Numbers::makeOne($internalScale+5)->setMode(CalcMode::Precision);
104 2
        $denominator = Numbers::make(Numbers::MUTABLE, '1', $internalScale)->setMode(CalcMode::Precision);
105 2
        $e = Numbers::make(NUmbers::MUTABLE, '2', $internalScale)->setMode(CalcMode::Precision);
106 2
        $n = Numbers::make(Numbers::MUTABLE, '2', $internalScale)->setMode(CalcMode::Precision);
107
108 2
        $continue = true;
109
110 2
        while ($continue) {
111 2
            $denominator->multiply($n);
112 2
            $n->add($one);
113 2
            $term = $one->divide($denominator);
114
115 2
            if ($term->numberOfLeadingZeros() > $internalScale || $term->isEqual(0)) {
116 2
                $continue = false;
117
            }
118
119 2
            $e->add($term);
120
        }
121
122 2
        self::$e = $e->truncateToScale($digits);
123
124 2
        return $e->truncateToScale($digits)->getValue(NumberBase::Ten);
125
126
    }
127
128
    /**
129
     * @param int $digits
130
     * @return string
131
     * @throws IntegrityConstraint
132
     */
133 4
    public static function makeGoldenRatio(int $digits): string
134
    {
135
136 4
        if ($digits < 5) {
137
            throw new IntegrityConstraint(
138
                'Cannot return so few digits of phi.',
139
                'For fewer digits of phi, use Numbers::makeGoldenRatio()'
140
            );
141
        }
142
143 4
        if (isset(self::$phi) && self::$phi->numberOfDecimalDigits() >= $digits) {
144 2
            return self::$phi->truncateToScale($digits)->getValue(NumberBase::Ten);
145
        }
146
147 2
        $fibIndex = floor($digits*2.5);
148
149 2
        [$fibSmall, $fibLarge] = SequenceProvider::nthFibonacciPair($fibIndex);
0 ignored issues
show
Bug introduced by
$fibIndex of type double is incompatible with the type integer expected by parameter $n of Samsara\Fermat\Core\Prov...der::nthFibonacciPair(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

149
        [$fibSmall, $fibLarge] = SequenceProvider::nthFibonacciPair(/** @scrutinizer ignore-type */ $fibIndex);
Loading history...
150
151 2
        self::$phi = $fibLarge->divide($fibSmall, $digits+2)->truncateToScale($digits);
152
153 2
        return self::$phi->getValue(NumberBase::Ten);
0 ignored issues
show
Unused Code introduced by
The call to Samsara\Fermat\Core\Type...erInterface::getValue() has too many arguments starting with Samsara\Fermat\Core\Enums\NumberBase::Ten. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

153
        return self::$phi->/** @scrutinizer ignore-call */ getValue(NumberBase::Ten);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
154
155
    }
156
157
    /**
158
     * @param int $digits
159
     * @return string
160
     */
161 2
    public static function makeIPowI(int $digits): string
162
    {
163
164 2
        if (isset(self::$ipowi) && self::$ipowi->numberOfDecimalDigits() >= $digits) {
165
            return self::$ipowi->truncateToScale($digits)->getValue(NumberBase::Ten);
166
        }
167
168 2
        $piDivTwo = Numbers::makePi($digits+2)->divide(2)->multiply(-1);
169
170 2
        $e = Numbers::makeE($digits+2);
171
172 2
        self::$ipowi = $e->pow($piDivTwo)->truncateToScale($digits);
173
174 2
        return self::$ipowi->getValue(NumberBase::Ten);
0 ignored issues
show
Unused Code introduced by
The call to Samsara\Fermat\Core\Type...erInterface::getValue() has too many arguments starting with Samsara\Fermat\Core\Enums\NumberBase::Ten. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

174
        return self::$ipowi->/** @scrutinizer ignore-call */ getValue(NumberBase::Ten);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
175
176
    }
177
178
    /**
179
     * The lnScale() implementation is very efficient, so this is probably our best bet for computing more digits of
180
     * ln(10) to provide.
181
     *
182
     * @param int $digits
183
     * @return string
184
     * @throws IntegrityConstraint
185
     */
186 4
    public static function makeLn10(int $digits): string
187
    {
188
189 4
        if (isset(self::$ln10) && self::$ln10->numberOfDecimalDigits() >= $digits) {
190 2
            return self::$ln10->truncateToScale($digits)->getValue(NumberBase::Ten);
191
        }
192
193 2
        $ln10 = Numbers::make(Numbers::IMMUTABLE, 10, $digits+2)->setMode(CalcMode::Precision);
194 2
        $ln10 = $ln10->ln();
195
196 2
        self::$ln10 = $ln10;
197
198 2
        return $ln10->truncateToScale($digits)->getValue(NumberBase::Ten);
199
200
    }
201
202
    /**
203
     * This function is a special case of the ln() function where x can be represented by (n + 1)/n, where n is an
204
     * integer. This particular special case converges extremely rapidly. For ln(2), n = 1.
205
     *
206
     * @param int $digits
207
     * @return string
208
     */
209 15
    public static function makeLn2(int $digits): string
210
    {
211
212 15
        if (isset(self::$ln2) && self::$ln2->numberOfDecimalDigits() >= $digits) {
213 12
            return self::$ln2->truncateToScale($digits)->getValue(NumberBase::Ten);
214
        }
215
216 3
        $twoThirds = Numbers::make(Numbers::IMMUTABLE, str_pad('0.', $digits+3, '6'));
217 3
        $nine = Numbers::make(Numbers::IMMUTABLE, 9, $digits+3);
218 3
        $ln2 = self::_makeLnSpecial($digits, $nine, $twoThirds);
219
220 3
        self::$ln2 = $ln2;
221
222 3
        return $ln2->getValue(NumberBase::Ten);
223
224
    }
225
226
    /**
227
     * This function is a special case of the ln() function where x can be represented by (n + 1)/n, where n is an
228
     * integer. This particular special case converges extremely rapidly. For ln(1.1), n = 10.
229
     *
230
     * @param int $digits
231
     * @return string
232
     */
233 46
    public static function makeLn1p1(int $digits): string
234
    {
235
236 46
        if (isset(self::$ln1p1) && self::$ln1p1->numberOfDecimalDigits() >= $digits) {
237 43
            return self::$ln1p1->truncateToScale($digits)->getValue(NumberBase::Ten);
238
        }
239
240 3
        $two = Numbers::make(Numbers::IMMUTABLE, 2, $digits+3);
241 3
        $twentyOne = Numbers::make(Numbers::IMMUTABLE, 21, $digits+3);
242 3
        $fourFortyOne = Numbers::make(Numbers::IMMUTABLE, 441, $digits+3);
243 3
        $twoDivTwentyOne = $two->divide($twentyOne);
244 3
        $ln1p1 = self::_makeLnSpecial($digits, $fourFortyOne, $twoDivTwentyOne);
245
246 3
        self::$ln1p1 = $ln1p1;
247
248 3
        return $ln1p1->getValue(NumberBase::Ten);
249
250
    }
251
252
    /**
253
     * @param int $digits
254
     * @param ImmutableDecimal $innerNum
255
     * @param ImmutableDecimal $outerNum
256
     * @return ImmutableDecimal
257
     */
258 4
    private static function _makeLnSpecial(int $digits, ImmutableDecimal $innerNum, ImmutableDecimal $outerNum): ImmutableDecimal
259
    {
260 4
        $one = Numbers::makeOne($digits+3);
261 4
        $two = Numbers::make(Numbers::IMMUTABLE, 2, $digits+3);
262 4
        $sum = Numbers::makeZero($digits+3);
263 4
        $k = 0;
264
265
        do {
266
267 4
            $diff = $one->divide($one->add($two->multiply($k))->multiply($innerNum->pow($k)), $digits+3)->truncate($digits+2);
268
269 4
            $sum = $sum->add($diff);
270
271 4
            $k++;
272
273 4
        } while (!$diff->isEqual(0));
274
275 4
        $lnSpecial = $outerNum->multiply($sum);
276 4
        return $lnSpecial->truncateToScale($digits);
277
    }
278
279
}