2
2
3
3
namespace Litipk \BigNumbers ;
4
4
5
+ use Litipk \BigNumbers \DecimalConstants as DecimalConstants ;
5
6
use Litipk \BigNumbers \InfiniteDecimal as InfiniteDecimal ;
6
7
7
8
use Litipk \Exceptions \NotImplementedException as NotImplementedException ;
@@ -66,17 +67,19 @@ public static function getNegativeInfinite()
66
67
/**
67
68
* Decimal "constructor".
68
69
*
69
- * @param mixed $value
70
- * @param integer $scale
70
+ * @param mixed $value
71
+ * @param integer $scale
72
+ * @param boolean $removeZeros If true then removes trailing zeros from the number representation
73
+ * @return Decimal
71
74
*/
72
- public static function create ($ value , $ scale = null )
75
+ public static function create ($ value , $ scale = null , $ removeZeros = false )
73
76
{
74
77
if (is_int ($ value )) {
75
- return self ::fromInteger ($ value, $ scale );
78
+ return self ::fromInteger ($ value );
76
79
} elseif (is_float ($ value )) {
77
- return self ::fromFloat ($ value , $ scale );
80
+ return self ::fromFloat ($ value , $ scale, $ removeZeros );
78
81
} elseif (is_string ($ value )) {
79
- return self ::fromString ($ value , $ scale );
82
+ return self ::fromString ($ value , $ scale, $ removeZeros );
80
83
} elseif ($ value instanceof Decimal) {
81
84
return self ::fromDecimal ($ value , $ scale );
82
85
} else {
@@ -90,12 +93,11 @@ public static function create($value, $scale = null)
90
93
91
94
/**
92
95
* @param integer $intValue
93
- * @param integer $scale
94
96
* @return Decimal
95
97
*/
96
- public static function fromInteger ($ intValue, $ scale = null )
98
+ public static function fromInteger ($ intValue )
97
99
{
98
- self ::paramsValidation ($ intValue , $ scale );
100
+ self ::paramsValidation ($ intValue , null );
99
101
100
102
if (!is_int ($ intValue )) {
101
103
throw new InvalidArgumentTypeException (
@@ -105,18 +107,16 @@ public static function fromInteger($intValue, $scale = null)
105
107
);
106
108
}
107
109
108
- return new Decimal (
109
- $ scale === null ? (string )$ intValue : bcadd ((string )$ intValue , '0 ' , $ scale ),
110
- $ scale === null ? 0 : $ scale
111
- );
110
+ return new Decimal ((string )$ intValue , 0 );
112
111
}
113
112
114
113
/**
115
114
* @param float $fltValue
116
115
* @param integer $scale
116
+ * @param boolean $removeZeros If true then removes trailing zeros from the number representation
117
117
* @return Decimal
118
118
*/
119
- public static function fromFloat ($ fltValue , $ scale = null )
119
+ public static function fromFloat ($ fltValue , $ scale = null , $ removeZeros = false )
120
120
{
121
121
self ::paramsValidation ($ fltValue , $ scale );
122
122
@@ -138,22 +138,23 @@ public static function fromFloat($fltValue, $scale = null)
138
138
);
139
139
}
140
140
141
- $ dec_scale = $ scale === null ?
142
- 8 :
143
- $ scale ;
141
+ $ scale = ($ scale === null ) ? 8 : $ scale ;
144
142
145
- return new Decimal (
146
- number_format ($ fltValue , $ dec_scale , '. ' , '' ),
147
- $ dec_scale
148
- );
143
+ $ strValue = number_format ($ fltValue , $ scale , '. ' , '' );
144
+ if ($ removeZeros ) {
145
+ $ strValue = self ::removeTrailingZeros ($ strValue , $ scale );
146
+ }
147
+
148
+ return new Decimal ($ strValue , $ scale );
149
149
}
150
150
151
151
/**
152
152
* @param string $strValue
153
153
* @param integer $scale
154
+ * @param boolean $removeZeros If true then removes trailing zeros from the number representation
154
155
* @return Decimal
155
156
*/
156
- public static function fromString ($ strValue , $ scale = null )
157
+ public static function fromString ($ strValue , $ scale = null , $ removeZeros = false )
157
158
{
158
159
self ::paramsValidation ($ strValue , $ scale );
159
160
@@ -184,10 +185,10 @@ public static function fromString($strValue, $scale = null)
184
185
}
185
186
186
187
$ value = self ::normalizeSign ($ captures [1 ]) . bcmul (
187
- $ captures [2 ],
188
- $ tmp_multiplier ,
189
- max ($ min_scale , $ scale !== null ? $ scale : 0 )
190
- );
188
+ $ captures [2 ],
189
+ $ tmp_multiplier ,
190
+ max ($ min_scale , $ scale !== null ? $ scale : 0 )
191
+ );
191
192
192
193
} else if (preg_match ('/([+\-]?)(inf|Inf|INF)/ ' , $ strValue , $ captures ) === 1 ) {
193
194
if ($ captures [1 ] === '- ' ) {
@@ -201,13 +202,15 @@ public static function fromString($strValue, $scale = null)
201
202
);
202
203
}
203
204
204
- if ($ scale !==null ) {
205
- $ dec_scale = $ scale ;
206
- } else {
207
- $ dec_scale = $ min_scale ;
205
+ $ scale = ($ scale !==null ) ? $ scale : $ min_scale ;
206
+ if ($ scale < $ min_scale ) {
207
+ $ value = self ::innerRound ($ value , $ scale );
208
+ }
209
+ if ($ removeZeros ) {
210
+ $ value = self ::removeTrailingZeros ($ value , $ scale );
208
211
}
209
212
210
- return new Decimal (self :: innerRound ( $ value , $ dec_scale ), $ dec_scale );
213
+ return new Decimal ($ value , $ scale );
211
214
}
212
215
213
216
/**
@@ -223,7 +226,7 @@ public static function fromDecimal(Decimal $decValue, $scale = null)
223
226
self ::paramsValidation ($ decValue , $ scale );
224
227
225
228
// This block protect us from unnecessary additional instances
226
- if ($ scale === null || $ scale == = $ decValue ->scale || $ decValue ->isInfinite ()) {
229
+ if ($ scale === null || $ scale > = $ decValue ->scale || $ decValue ->isInfinite ()) {
227
230
return $ decValue ;
228
231
}
229
232
@@ -286,7 +289,7 @@ public function mul(Decimal $b, $scale = null)
286
289
if ($ b ->isInfinite ()) {
287
290
return $ b ->mul ($ this );
288
291
} elseif ($ b ->isZero ()) {
289
- return Decimal:: fromInteger ( 0 , $ scale );
292
+ return DecimalConstants:: Zero ( );
290
293
}
291
294
292
295
return self ::fromString (
@@ -311,10 +314,8 @@ public function div(Decimal $b, $scale = null)
311
314
312
315
if ($ b ->isZero ()) {
313
316
throw new \DomainException ("Division by zero is not allowed. " );
314
- } elseif ($ this ->isZero ()) {
315
- return self ::fromDecimal ($ this , $ scale );
316
- } elseif ($ b ->isInfinite ()) {
317
- return Decimal::fromInteger (0 , $ scale );
317
+ } elseif ($ this ->isZero () || $ b ->isInfinite ()) {
318
+ return DecimalConstants::Zero ();
318
319
} else {
319
320
if ($ scale !== null ) {
320
321
$ divscale = $ scale ;
@@ -327,19 +328,18 @@ public function div(Decimal $b, $scale = null)
327
328
self ::innerLog10 ($ this_abs ->value , $ this_abs ->scale , 1 ) -
328
329
self ::innerLog10 ($ b_abs ->value , $ b_abs ->scale , 1 );
329
330
330
- $ divscale = max (
331
+ $ divscale = ( int ) max (
331
332
$ this ->scale + $ b ->scale ,
332
333
max (
333
334
self ::countSignificativeDigits ($ this , $ this_abs ),
334
335
self ::countSignificativeDigits ($ b , $ b_abs )
335
336
) - max (ceil ($ log10_result ), 0 ),
336
- ceil (-$ log10_result )
337
+ ceil (-$ log10_result ) + 1
337
338
);
338
339
}
339
340
340
341
return self ::fromString (
341
- bcdiv ($ this ->value , $ b ->value , $ divscale +1 ),
342
- $ divscale
342
+ bcdiv ($ this ->value , $ b ->value , $ divscale +1 ), $ divscale
343
343
);
344
344
}
345
345
}
@@ -356,7 +356,7 @@ public function sqrt($scale = null)
356
356
"Decimal can't handle square roots of negative numbers (it's only for real numbers). "
357
357
);
358
358
} elseif ($ this ->isZero ()) {
359
- return Decimal:: fromDecimal ( $ this , $ scale );
359
+ return DecimalConstants:: Zero ( );
360
360
}
361
361
362
362
$ sqrt_scale = ($ scale !== null ? $ scale : $ this ->scale );
@@ -385,7 +385,11 @@ public function pow(Decimal $b, $scale = null)
385
385
);
386
386
}
387
387
} elseif ($ b ->isZero ()) {
388
- return Decimal::fromInteger (1 , $ scale );
388
+ return DecimalConstants::One ();
389
+ } else if ($ b ->isNegative ()) {
390
+ return DecimalConstants::One ()->div (
391
+ $ this ->pow ($ b ->additiveInverse ()), $ scale
392
+ );
389
393
} elseif ($ b ->scale == 0 ) {
390
394
$ pow_scale = $ scale === null ?
391
395
max ($ this ->scale , $ b ->scale ) : max ($ this ->scale , $ b ->scale , $ scale );
@@ -415,6 +419,16 @@ public function pow(Decimal $b, $scale = null)
415
419
$ pow_scale
416
420
);
417
421
} else { // elseif ($this->isNegative())
422
+ if ($ b ->isInteger ()) {
423
+ if (preg_match ('/^[+\-]?[0-9]*[02468](\.0+)?$/ ' , $ b ->value , $ captures ) === 1 ) {
424
+ // $b is an even number
425
+ return $ this ->additiveInverse ()->pow ($ b , $ scale );
426
+ } else {
427
+ // $b is an odd number
428
+ return $ this ->additiveInverse ()->pow ($ b , $ scale )->additiveInverse ();
429
+ }
430
+ }
431
+
418
432
throw new NotImplementedException (
419
433
"Usually negative numbers can't be powered to non integer numbers. " .
420
434
"The cases where is possible are not implemented. "
@@ -445,6 +459,7 @@ public function log10($scale = null)
445
459
}
446
460
447
461
/**
462
+ * @param integer $scale
448
463
* @return boolean
449
464
*/
450
465
public function isZero ($ scale = null )
@@ -470,6 +485,14 @@ public function isNegative()
470
485
return ($ this ->value [0 ] === '- ' );
471
486
}
472
487
488
+ /**
489
+ * @return boolean
490
+ */
491
+ public function isInteger ()
492
+ {
493
+ return (preg_match ('/^[+\-]?[0-9]+(\.0+)?$/ ' , $ this ->value , $ captures ) === 1 );
494
+ }
495
+
473
496
/**
474
497
* @return boolean
475
498
*/
@@ -509,6 +532,7 @@ public function equals(Decimal $b, $scale = null)
509
532
* $this > $b : returns 1 , $this < $b : returns -1 , $this == $b : returns 0
510
533
*
511
534
* @param Decimal $b
535
+ * @param integer $scale
512
536
* @return integer
513
537
*/
514
538
public function comp (Decimal $ b , $ scale = null )
@@ -657,20 +681,20 @@ public function sin($scale = null) {
657
681
$ x = $ this ->mod (DecimalConstants::PI ()->mul (Decimal::fromString ("2 " )));
658
682
659
683
// PI has only 32 significant numbers
660
- $ significantNumbers = is_null ($ scale ) ? 32 : $ scale ;
684
+ $ significantNumbers = ($ scale === null ) ? 32 : $ scale ;
661
685
662
686
// Next use Maclaurin's theorem to approximate sin with high enough accuracy
663
687
// note that the accuracy is depended on the accuracy of the given PI constant
664
- $ faculty = Decimal:: fromString ( " 1 " ); // Calculates the faculty under the sign
665
- $ xPowerN = Decimal:: fromString ( " 1 " ); // Calculates x^n
666
- $ approx = Decimal:: fromString ( " 0 " ); // keeps track of our approximation for sin(x)
688
+ $ faculty = DecimalConstants:: One ( ); // Calculates the faculty under the sign
689
+ $ xPowerN = DecimalConstants:: One ( ); // Calculates x^n
690
+ $ approx = DecimalConstants:: Zero ( ); // keeps track of our approximation for sin(x)
667
691
668
692
$ change = InfiniteDecimal::getPositiveInfinite ();
669
693
670
694
for ($ i = 1 ; !$ change ->round ($ significantNumbers )->isZero (); $ i ++) {
671
695
// update x^n and n! for this walkthrough
672
696
$ xPowerN = $ xPowerN ->mul ($ x );
673
- $ faculty = $ faculty ->mul (Decimal::fromString (( string ) $ i ));
697
+ $ faculty = $ faculty ->mul (Decimal::fromInteger ( $ i ));
674
698
675
699
// only do calculations if n is uneven
676
700
// otherwise result is zero anyways
@@ -702,20 +726,20 @@ public function cos($scale = null) {
702
726
$ x = $ this ->mod (DecimalConstants::PI ()->mul (Decimal::fromString ("2 " )));
703
727
704
728
// PI has only 32 significant numbers
705
- $ significantNumbers = is_null ($ scale ) ? 32 : $ scale ;
729
+ $ significantNumbers = ($ scale === null ) ? 32 : $ scale ;
706
730
707
731
// Next use Maclaurin's theorem to approximate sin with high enough accuracy
708
732
// note that the accuracy is depended on the accuracy of the given PI constant
709
- $ faculty = Decimal:: fromString ( " 1 " ); // Calculates the faculty under the sign
710
- $ xPowerN = Decimal:: fromString ( " 1 " ); // Calculates x^n
711
- $ approx = Decimal:: fromString ( " 1 " ); // keeps track of our approximation for sin(x)
733
+ $ faculty = DecimalConstants:: One ( ); // Calculates the faculty under the sign
734
+ $ xPowerN = DecimalConstants:: One ( ); // Calculates x^n
735
+ $ approx = DecimalConstants:: One ( ); // keeps track of our approximation for sin(x)
712
736
713
737
$ change = InfiniteDecimal::getPositiveInfinite ();
714
738
715
739
for ($ i = 1 ; !$ change ->floor ($ significantNumbers )->isZero (); $ i ++) {
716
740
// update x^n and n! for this walkthrough
717
741
$ xPowerN = $ xPowerN ->mul ($ x );
718
- $ faculty = $ faculty ->mul (Decimal::fromString (( string ) $ i ));
742
+ $ faculty = $ faculty ->mul (Decimal::fromInteger ( $ i ));
719
743
720
744
// only do calculations if n is uneven
721
745
// otherwise result is zero anyways
@@ -989,19 +1013,31 @@ private static function normalizeSign($sign)
989
1013
return $ sign ;
990
1014
}
991
1015
1016
+ private static function removeTrailingZeros ($ strValue , &$ scale )
1017
+ {
1018
+ preg_match ('/^[+\-]?[0-9]+(\.([0-9]*[1-9])?(0+)?)?$/ ' , $ strValue , $ captures );
1019
+
1020
+ if (count ($ captures ) === 4 ) {
1021
+ $ toRemove = strlen ($ captures [3 ]);
1022
+ $ scale = strlen ($ captures [2 ]);
1023
+ $ strValue = substr ($ strValue , 0 , strlen ($ strValue )-$ toRemove -($ scale ===0 ? 1 : 0 ));
1024
+ }
1025
+
1026
+ return $ strValue ;
1027
+ }
1028
+
992
1029
/**
993
- * Counts the number of significative digits of $val
1030
+ * Counts the number of significative digits of $val.
1031
+ * Assumes a consistent internal state (without zeros at the end or the start).
994
1032
*
995
1033
* @param Decimal $val
996
1034
* @param Decimal $abs $val->abs()
997
1035
* @return integer
998
1036
*/
999
1037
private static function countSignificativeDigits (Decimal $ val , Decimal $ abs )
1000
1038
{
1001
- $ one = Decimal::fromInteger (1 );
1002
-
1003
1039
return strlen ($ val ->value ) - (
1004
- ($ abs ->comp ($ one ) === -1 ) ? 2 : max ($ val ->scale , 1 )
1040
+ ($ abs ->comp (DecimalConstants:: One () ) === -1 ) ? 2 : max ($ val ->scale , 1 )
1005
1041
) - ($ val ->isNegative () ? 1 : 0 );
1006
1042
}
1007
1043
}
0 commit comments