1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Litipk\BigNumbers; |
4
|
|
|
|
5
|
|
|
use Litipk\BigNumbers\Decimal as Decimal; |
6
|
|
|
use Litipk\BigNumbers\DecimalConstants as DecimalConstants; |
7
|
|
|
use Litipk\Exceptions\InvalidCastException; |
8
|
|
|
use Litipk\Exceptions\NotImplementedException; |
9
|
|
|
|
10
|
|
|
/** |
11
|
|
|
* Immutable object that represents an infinite number |
12
|
|
|
* |
13
|
|
|
* @author Andreu Correa Casablanca <[email protected]> |
14
|
|
|
*/ |
15
|
|
|
class InfiniteDecimal extends Decimal |
16
|
|
|
{ |
17
|
|
|
/** |
18
|
|
|
* Single instance of "Positive Infinite" |
19
|
|
|
* @var Decimal |
20
|
|
|
*/ |
21
|
|
|
private static $pInf = null; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Single instance of "Negative Infinite" |
25
|
|
|
* @var Decimal |
26
|
|
|
*/ |
27
|
|
|
private static $nInf = null; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Private constructor |
31
|
|
|
* @param string $value |
32
|
|
|
*/ |
33
|
2 |
|
private function __construct($value) |
|
|
|
|
34
|
|
|
{ |
35
|
2 |
|
$this->value = $value; |
36
|
2 |
|
} |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* Private clone method |
40
|
|
|
*/ |
41
|
|
|
private function __clone() |
|
|
|
|
42
|
|
|
{ |
43
|
|
|
|
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* Returns a "Positive Infinite" object |
48
|
|
|
* @return Decimal |
49
|
|
|
*/ |
50
|
73 |
|
public static function getPositiveInfinite() |
51
|
|
|
{ |
52
|
73 |
|
if (self::$pInf === null) { |
53
|
1 |
|
self::$pInf = new InfiniteDecimal('INF'); |
54
|
1 |
|
} |
55
|
|
|
|
56
|
73 |
|
return self::$pInf; |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* Returns a "Negative Infinite" object |
61
|
|
|
* @return Decimal |
62
|
|
|
*/ |
63
|
42 |
|
public static function getNegativeInfinite() |
64
|
|
|
{ |
65
|
42 |
|
if (self::$nInf === null) { |
66
|
1 |
|
self::$nInf = new InfiniteDecimal('-INF'); |
67
|
1 |
|
} |
68
|
|
|
|
69
|
42 |
|
return self::$nInf; |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* Adds two Decimal objects |
74
|
|
|
* @param Decimal $b |
75
|
|
|
* @param integer $scale |
76
|
|
|
* @return Decimal |
77
|
|
|
*/ |
78
|
2 |
|
public function add(Decimal $b, $scale = null) |
79
|
|
|
{ |
80
|
2 |
|
self::paramsValidation($b, $scale); |
81
|
|
|
|
82
|
2 |
|
if (!$b->isInfinite()) { |
83
|
1 |
|
return $this; |
84
|
1 |
|
} elseif ($this->hasSameSign($b)) { |
85
|
1 |
|
return $this; |
86
|
|
|
} else { // elseif ($this->isPositive() && $b->isNegative || $this->isNegative() && $b->isPositive()) { |
87
|
1 |
|
throw new \DomainException("Infinite numbers with opposite signs can't be added."); |
88
|
|
|
} |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* Subtracts two BigNumber objects |
93
|
|
|
* @param Decimal $b |
94
|
|
|
* @param integer $scale |
95
|
|
|
* @return Decimal |
96
|
|
|
*/ |
97
|
2 |
|
public function sub(Decimal $b, $scale = null) |
98
|
|
|
{ |
99
|
2 |
|
self::paramsValidation($b, $scale); |
100
|
|
|
|
101
|
2 |
|
if (!$b->isInfinite()) { |
102
|
1 |
|
return $this; |
103
|
1 |
|
} elseif (!$this->hasSameSign($b)) { |
104
|
1 |
|
return $this; |
105
|
|
|
} else { // elseif () { |
106
|
1 |
|
throw new \DomainException("Infinite numbers with the same sign can't be subtracted."); |
107
|
|
|
} |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* Multiplies two BigNumber objects |
112
|
|
|
* @param Decimal $b |
113
|
|
|
* @param integer $scale |
114
|
|
|
* @return Decimal |
115
|
|
|
*/ |
116
|
5 |
|
public function mul(Decimal $b, $scale = null) |
117
|
|
|
{ |
118
|
5 |
|
self::paramsValidation($b, $scale); |
119
|
|
|
|
120
|
5 |
|
if ($b->isZero()) { |
121
|
4 |
|
throw new \DomainException("Zero multiplied by infinite is not allowed."); |
122
|
|
|
} |
123
|
|
|
|
124
|
1 |
|
if ($this->hasSameSign($b)) { |
125
|
1 |
|
return self::getPositiveInfinite(); |
126
|
|
|
} else { // elseif (!$this->hasSameSign($b)) { |
127
|
1 |
|
return self::getNegativeInfinite(); |
128
|
|
|
} |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* Divides the object by $b . |
133
|
|
|
* Warning: div with $scale == 0 is not the same as |
134
|
|
|
* integer division because it rounds the |
135
|
|
|
* last digit in order to minimize the error. |
136
|
|
|
* |
137
|
|
|
* @param Decimal $b |
138
|
|
|
* @param integer $scale |
139
|
|
|
* @return Decimal |
140
|
|
|
*/ |
141
|
3 |
|
public function div(Decimal $b, $scale = null) |
142
|
|
|
{ |
143
|
3 |
|
self::paramsValidation($b, $scale); |
144
|
|
|
|
145
|
3 |
|
if ($b->isZero()) { |
146
|
1 |
|
throw new \DomainException("Division by zero is not allowed."); |
147
|
2 |
|
} elseif ($b->isInfinite()) { |
148
|
1 |
|
throw new \DomainException("Infinite divided by Infinite is not allowed."); |
149
|
1 |
|
} elseif ($b->isPositive()) { |
150
|
1 |
|
return $this; |
151
|
|
|
} else { //if ($b->isNegative()) { |
152
|
1 |
|
return $this->additiveInverse(); |
153
|
|
|
} |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* Returns the square root of this object |
158
|
|
|
* @param integer $scale |
159
|
|
|
* @return Decimal |
160
|
|
|
*/ |
161
|
2 |
|
public function sqrt($scale = null) |
162
|
|
|
{ |
163
|
2 |
|
if ($this->isNegative()) { |
164
|
1 |
|
throw new \DomainException( |
165
|
|
|
"Decimal can't handle square roots of negative numbers (it's only for real numbers)." |
166
|
1 |
|
); |
167
|
|
|
} |
168
|
|
|
|
169
|
1 |
|
return $this; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
/** |
173
|
|
|
* Powers this value to $b |
174
|
|
|
* |
175
|
|
|
* @param Decimal $b exponent |
176
|
|
|
* @param integer $scale |
177
|
|
|
* @return Decimal |
178
|
|
|
*/ |
179
|
7 |
|
public function pow(Decimal $b, $scale = null) |
180
|
|
|
{ |
181
|
7 |
|
if ($b->isPositive()) { |
182
|
3 |
|
if ($this->isPositive()) { |
183
|
1 |
|
return $this; |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
// if ($this->isNegative()) |
187
|
2 |
|
if ($b->isInfinite()) { |
188
|
1 |
|
throw new \DomainException("Negative infinite elevated to infinite is undefined."); |
189
|
|
|
} |
190
|
|
|
|
191
|
1 |
|
if ($b->isInteger()) { |
192
|
1 |
|
if (preg_match('/^[+\-]?[0-9]*[02468](\.0+)?$/', $b->value, $captures) === 1) { |
193
|
|
|
// $b is an even number |
194
|
1 |
|
return self::$pInf; |
195
|
|
|
} else { |
196
|
|
|
// $b is an odd number |
197
|
1 |
|
return $this; // Negative Infinite |
198
|
|
|
} |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
throw new NotImplementedException("See issues #21, #22, #23 and #24 on Github."); |
202
|
|
|
|
203
|
4 |
|
} else if ($b->isNegative()) { |
204
|
2 |
|
return DecimalConstants::Zero(); |
205
|
2 |
|
} else if ($b->isZero()) { |
206
|
2 |
|
throw new \DomainException("Infinite elevated to zero is undefined."); |
207
|
|
|
} |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
/** |
211
|
|
|
* Returns the object's logarithm in base 10 |
212
|
|
|
* @param integer $scale |
213
|
|
|
* @return Decimal |
214
|
|
|
*/ |
215
|
2 |
|
public function log10($scale = null) |
216
|
|
|
{ |
217
|
2 |
|
if ($this->isNegative()) { |
218
|
1 |
|
throw new \DomainException( |
219
|
|
|
"Decimal can't handle logarithms of negative numbers (it's only for real numbers)." |
220
|
1 |
|
); |
221
|
|
|
} |
222
|
|
|
|
223
|
1 |
|
return $this; |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
/** |
227
|
|
|
* Equality comparison between this object and $b |
228
|
|
|
* @param Decimal $b |
229
|
|
|
* @param integer $scale |
230
|
|
|
* @return boolean |
231
|
|
|
*/ |
232
|
19 |
|
public function equals(Decimal $b, $scale = null) |
233
|
|
|
{ |
234
|
19 |
|
return ($this === $b); |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
/** |
238
|
|
|
* $this > $b : returns 1 , $this < $b : returns -1 , $this == $b : returns 0 |
239
|
|
|
* |
240
|
|
|
* @param Decimal $b |
241
|
|
|
* @param integer $scale |
242
|
|
|
* @return integer |
243
|
|
|
*/ |
244
|
2 |
|
public function comp(Decimal $b, $scale = null) |
245
|
|
|
{ |
246
|
2 |
|
self::paramsValidation($b, $scale); |
247
|
|
|
|
248
|
2 |
|
if ($this === $b) { |
249
|
1 |
|
return 0; |
250
|
2 |
|
} elseif ($this === self::getPositiveInfinite()) { |
251
|
2 |
|
return 1; |
252
|
|
|
} else { // if ($this === self::getNegativeInfinite()) { |
253
|
2 |
|
return -1; |
254
|
|
|
} |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* Returns the element's additive inverse. |
259
|
|
|
* @return Decimal |
260
|
|
|
*/ |
261
|
4 |
|
public function additiveInverse() |
262
|
|
|
{ |
263
|
4 |
|
if ($this === self::getPositiveInfinite()) { |
264
|
3 |
|
return self::$nInf; |
265
|
|
|
} else { // if ($this === self::getNegativeInfinite()) { |
266
|
4 |
|
return self::$pInf; |
267
|
|
|
} |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
/** |
271
|
|
|
* "Rounds" the Decimal to have at most $scale digits after the point |
272
|
|
|
* @param integer $scale |
273
|
|
|
* @return Decimal |
274
|
|
|
*/ |
275
|
1 |
|
public function round($scale = 0) |
276
|
|
|
{ |
277
|
1 |
|
return $this; |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
/** |
281
|
|
|
* "Ceils" the Decimal to have at most $scale digits after the point |
282
|
|
|
* @param integer $scale |
283
|
|
|
* @return Decimal |
284
|
|
|
*/ |
285
|
1 |
|
public function ceil($scale = 0) |
286
|
|
|
{ |
287
|
1 |
|
return $this; |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* "Floors" the Decimal to have at most $scale digits after the point |
292
|
|
|
* @param integer $scale |
293
|
|
|
* @return Decimal |
294
|
|
|
*/ |
295
|
33 |
|
public function floor($scale = 0) |
296
|
|
|
{ |
297
|
33 |
|
return $this; |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
/** |
301
|
|
|
* Throws exception because sine is undefined in the infinite. |
302
|
|
|
* |
303
|
|
|
* @param integer $scale |
304
|
|
|
* @return null |
305
|
|
|
*/ |
306
|
2 |
|
public function sin($scale = null) |
307
|
|
|
{ |
308
|
2 |
|
throw new \DomainException(($this === self::$pInf) ? |
309
|
2 |
|
"Sine function hasn't limit in the positive infinite." : |
310
|
|
|
"Sine function hasn't limit in the negative infinite." |
311
|
2 |
|
); |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
/** |
315
|
|
|
* Throws exception because cosecant is undefined in the infinite. |
316
|
|
|
* |
317
|
|
|
* @param integer $scale |
318
|
|
|
* @return null |
319
|
|
|
*/ |
320
|
2 |
|
public function cosec($scale = null) |
321
|
|
|
{ |
322
|
2 |
|
throw new \DomainException(($this === self::$pInf) ? |
323
|
2 |
|
"Cosecant function hasn't limit in the positive infinite." : |
324
|
|
|
"Cosecant function hasn't limit in the negative infinite." |
325
|
2 |
|
); |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
/** |
329
|
|
|
* Throws exception because cosine is undefined in the infinite. |
330
|
|
|
* |
331
|
|
|
* @param integer $scale |
332
|
|
|
* @return null |
333
|
|
|
*/ |
334
|
2 |
|
public function cos($scale = null) |
335
|
|
|
{ |
336
|
2 |
|
throw new \DomainException(($this === self::$pInf) ? |
337
|
2 |
|
"Cosine function hasn't limit in the positive infinite." : |
338
|
|
|
"Cosine function hasn't limit in the negative infinite." |
339
|
2 |
|
); |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
/** |
343
|
|
|
* Throws exception because secant is undefined in the infinite. |
344
|
|
|
* |
345
|
|
|
* @param integer $scale |
346
|
|
|
* @return null |
347
|
|
|
*/ |
348
|
2 |
|
public function sec($scale = null) |
349
|
|
|
{ |
350
|
2 |
|
throw new \DomainException(($this === self::$pInf) ? |
351
|
2 |
|
"Secant function hasn't limit in the positive infinite." : |
352
|
|
|
"Secant function hasn't limit in the negative infinite." |
353
|
2 |
|
); |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
/** |
357
|
|
|
* Returns exp($this), said in other words: e^$this . |
358
|
|
|
* |
359
|
|
|
* @param integer $scale |
360
|
|
|
* @return Decimal |
361
|
|
|
*/ |
362
|
2 |
|
public function exp($scale = null) |
363
|
|
|
{ |
364
|
2 |
|
if ($this == self::$pInf) { |
365
|
1 |
|
return $this; |
366
|
|
|
} else { |
367
|
1 |
|
return DecimalConstants::zero(); |
368
|
|
|
} |
369
|
|
|
} |
370
|
|
|
|
371
|
2 |
|
public function tan($scale = null) |
372
|
|
|
{ |
373
|
2 |
|
throw new \DomainException(($this === self::$pInf) ? |
374
|
2 |
|
"Tangent function hasn't limit in the positive infinite." : |
375
|
|
|
"Tangent function hasn't limit in the negative infinite." |
376
|
2 |
|
); |
377
|
|
|
} |
378
|
|
|
|
379
|
2 |
|
public function cotan($scale = null) |
380
|
|
|
{ |
381
|
2 |
|
throw new \DomainException(($this === self::$pInf) ? |
382
|
2 |
|
"Cotangent function hasn't limit in the positive infinite." : |
383
|
|
|
"Cotangent function hasn't limit in the negative infinite." |
384
|
2 |
|
); |
385
|
|
|
} |
386
|
|
|
|
387
|
|
|
/** |
388
|
|
|
* @param integer $scale Has no effect, exists only for compatibility. |
389
|
|
|
* @return boolean |
390
|
|
|
*/ |
391
|
36 |
|
public function isZero($scale = null) |
392
|
|
|
{ |
393
|
36 |
|
return false; |
394
|
|
|
} |
395
|
|
|
|
396
|
|
|
/** |
397
|
|
|
* @return boolean |
398
|
|
|
*/ |
399
|
11 |
|
public function isPositive() |
400
|
|
|
{ |
401
|
11 |
|
return ($this === self::$pInf); |
402
|
|
|
} |
403
|
|
|
|
404
|
|
|
/** |
405
|
|
|
* @return boolean |
406
|
|
|
*/ |
407
|
12 |
|
public function isNegative() |
408
|
|
|
{ |
409
|
12 |
|
return ($this === self::$nInf); |
410
|
|
|
} |
411
|
|
|
|
412
|
|
|
/** |
413
|
|
|
* @return boolean |
414
|
|
|
*/ |
415
|
|
|
public function isInteger() |
416
|
|
|
{ |
417
|
|
|
return false; |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
/** |
421
|
|
|
* @return boolean |
422
|
|
|
*/ |
423
|
13 |
|
public function isInfinite() |
424
|
|
|
{ |
425
|
13 |
|
return true; |
426
|
|
|
} |
427
|
|
|
|
428
|
|
|
/** |
429
|
|
|
* Return value as a float |
430
|
|
|
* |
431
|
|
|
* @return float |
432
|
|
|
*/ |
433
|
1 |
|
public function asFloat() |
434
|
|
|
{ |
435
|
1 |
|
return ($this === self::$pInf) ? INF : -INF; |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
/** |
439
|
|
|
* Return value as a integer |
440
|
|
|
* |
441
|
|
|
* @return float |
442
|
|
|
*/ |
443
|
1 |
|
public function asInteger() |
444
|
|
|
{ |
445
|
1 |
|
throw new InvalidCastException("InfiniteDecimal", "int", "PHP integers can't represent infinite values."); |
446
|
|
|
} |
447
|
|
|
} |
448
|
|
|
|
Overwriting private methods is generally fine as long as you also use private visibility. It might still be preferable for understandability to use a different method name.