NumberTool   F
last analyzed

Complexity

Total Complexity 79

Size/Duplication

Total Lines 530
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 152
c 5
b 0
f 0
dl 0
loc 530
rs 2.08
wmc 79

35 Methods

Rating   Name   Duplication   Size   Complexity  
A numberToText() 0 16 2
A addVat() 0 3 1
A gte() 0 4 2
A round() 0 3 1
A sub() 0 4 2
A pow() 0 5 2
A lt() 0 3 1
A roundDown() 0 8 2
A format() 0 3 1
A truncate() 0 10 3
A calculateMedian() 0 14 3
A beforePercentAddition() 0 5 2
A isNullOrZero() 0 3 2
A div() 0 4 2
A pmt() 0 5 1
A abs() 0 12 3
A percent() 0 4 2
A gt() 0 3 1
A lte() 0 4 2
A addAll() 0 7 2
A calculateAverage() 0 11 3
A add() 0 4 2
A isZero() 0 3 1
A addPercent() 0 4 2
A mul() 0 4 2
A f() 0 3 1
A floor() 0 3 1
A removeVat() 0 3 1
A vatAmount() 0 4 1
A getRoundedValueAndDifference() 0 8 1
A eq() 0 3 1
A getPercentageBetweenTwo() 0 7 2
A roundCustom() 0 3 1
B result() 0 36 8
C getResult() 0 44 15

How to fix   Complexity   

Complex Class

Complex classes like NumberTool often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use NumberTool, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Common\Tool;
4
5
/**
6
 * Utility for financial calculations
7
 */
8
class NumberTool
9
{
10
    protected const SCALE = 10;
11
12
    /**
13
     * Convert numeric variable to string with right formatting
14
     * @param mixed $s
15
     * @return string
16
     */
17
    private static function f($s)
18
    {
19
        return sprintf('%.' . self::SCALE . 'f', $s);
20
    }
21
22
    /**
23
     * Performs addition
24
     * NumberTool::add('2.71', '3.18') //5.89
25
     * @param string $op1
26
     * @param string $op2
27
     * @param boolean $round
28
     * @return string
29
     */
30
    public static function add($op1, $op2, $round = true)
31
    {
32
        $res = bcadd(self::f($op1), self::f($op2), self::SCALE);
33
        return $round ? self::round($res) : $res;
34
    }
35
36
    /**
37
     * Performs substraction
38
     * NumberTool::sub('5.89', '3.18') //2.71
39
     * @param string $op1
40
     * @param string $op2
41
     * @param boolean $round
42
     * @return string
43
     */
44
    public static function sub($op1, $op2, $round = true)
45
    {
46
        $res = bcsub(self::f($op1), self::f($op2), self::SCALE);
47
        return $round ? self::round($res) : $res;
48
    }
49
50
    /**
51
     * Performs multiplication
52
     * NumberTool::mul('16.69', '12.47') //208.12
53
     * @param string $op1
54
     * @param string $op2
55
     * @param boolean $round
56
     * @return string
57
     */
58
    public static function mul($op1, $op2, $round = true)
59
    {
60
        $res = bcmul(self::f($op1), self::f($op2), self::SCALE);
61
        return $round ? self::round($res) : $res;
62
    }
63
64
    /**
65
     * Performs division
66
     * NumberTool::div('208.12', '16.69') //12.47
67
     * @param string $op1
68
     * @param string $op2
69
     * @param boolean $round
70
     * @return string
71
     */
72
    public static function div($op1, $op2, $round = true)
73
    {
74
        $res = bcdiv(self::f($op1), self::f($op2), self::SCALE);
75
        return $round ? self::round($res) : $res;
76
    }
77
78
    /**
79
     * Rise $left to $right
80
     * @param string $left left operand
81
     * @param string $right right operand
82
     * @param boolean $round
83
     * @return string|float|int
84
     */
85
    public static function pow($left, $right, $round = true)
86
    {
87
        //bcpow does not support decimal numbers
88
        $res = $left ** $right;
89
        return $round ? self::round($res) : $res;
90
    }
91
92
    /**
93
     * Truncates decimal number to given precision
94
     * NumberTool::truncate('1.9999', 2) //1.99
95
     * @param string $number
96
     * @param integer $precision
97
     * @return string
98
     */
99
    public static function truncate($number, $precision)
100
    {
101
        $x = explode('.', $number);
102
        if (count($x) === 1) {
103
            return $x[0];
104
        }
105
        if ($precision === 0) {
106
            return $x[0];
107
        }
108
        return $x[0] . '.' . substr($x[1], 0, $precision);
109
    }
110
111
    /**
112
     * Absolute number value
113
     * NumberTool::abs('-10.99') //10.99
114
     * @param string $number
115
     * @return string
116
     */
117
    public static function abs($number)
118
    {
119
        $number = self::f($number);
120
        if ($number === '') {
121
            return $number;
122
        }
123
124
        if ($number[0] !== '-') {
125
            return $number;
126
        }
127
128
        return substr($number, 1);
129
    }
130
131
    /**
132
     * Rounds number with precision of $precision decimal places
133
     * NumberTool::round('208.1243') //208.12
134
     * @param string|int|float $val
135
     * @param integer $precision
136
     * @return string
137
     */
138
    public static function round($val, int $precision = 2): string
139
    {
140
        return number_format(round($val, $precision), $precision, '.', '');
0 ignored issues
show
Bug introduced by
It seems like $val can also be of type string; however, parameter $num of round() does only seem to accept double|integer, maybe add an additional type check? ( Ignorable by Annotation )

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

140
        return number_format(round(/** @scrutinizer ignore-type */ $val, $precision), $precision, '.', '');
Loading history...
141
    }
142
143
    /**
144
     * Formats number to decimal
145
     *
146
     * @param string $val
147
     * @param integer $precision
148
     * @return string
149
     */
150
    public static function format($val, $precision = 2)
151
    {
152
        return number_format($val, $precision, '.', '');
0 ignored issues
show
Bug introduced by
$val of type string is incompatible with the type double expected by parameter $num of number_format(). ( Ignorable by Annotation )

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

152
        return number_format(/** @scrutinizer ignore-type */ $val, $precision, '.', '');
Loading history...
153
    }
154
155
    /**
156
     * Rounds down number with precision of $precision decimal places
157
     * NumberTool::roundDown('2.03717') //2.03
158
     * @param string $val
159
     * @param integer $precision
160
     * @return string
161
     */
162
    public static function roundDown($val, $precision = 2)
163
    {
164
        if (self::isZero($val)) {
165
            return self::round($val, $precision);
166
        }
167
168
        $half = 0.5 / (10 ** $precision);
169
        return number_format(round($val - $half, $precision), $precision, '.', '');
170
    }
171
172
    /**
173
     * Floor
174
     * @param string $val
175
     * @return string
176
     */
177
    public static function floor($val)
178
    {
179
        return self::truncate($val, 0);
180
    }
181
182
    /**
183
     * Rounds number with custom precision and custom format
184
     * Example: NumberTool::round('208.1243', 2) // 208.12
185
     *
186
     * @access public
187
     * @param numeric $val
0 ignored issues
show
Bug introduced by
The type Common\Tool\numeric was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
188
     * @param int $precision
189
     * @return string
190
     */
191
    public static function roundCustom($val, int $precision = 1): string
192
    {
193
        return self::round($val, $precision);
194
    }
195
196
    /**
197
     * Calculates percentage
198
     * NumberTool::percent('19.99', '21.00') //4.20
199
     * @param string $amount
200
     * @param string $percentage
201
     * @param boolean $round
202
     * @return string
203
     */
204
    public static function percent($amount, $percentage, $round = true)
205
    {
206
        $res = bcmul(self::f($amount), bcdiv(self::f($percentage), '100', self::SCALE), self::SCALE);
207
        return $round ? self::round($res) : $res;
208
    }
209
210
    /**
211
     * NumberTool::addPercent('19.99', '21.00') //24.19
212
     * @param string $amount
213
     * @param string $percentage
214
     * @return string
215
     */
216
    public static function addPercent($amount, $percentage, $round = true)
217
    {
218
        $res = bcadd(self::f($amount), self::percent(self::f($amount), self::f($percentage)), self::SCALE);
219
        return $round ? self::round($res) : $res;
220
    }
221
222
    /**
223
     * NumberTool::beforePercentAddition('24.19', '21.00') //19.99
224
     * @param string $result
225
     * @param string $percentage
226
     * @return string
227
     */
228
    public static function beforePercentAddition($result, $percentage, $round = true)
229
    {
230
        $res = bcmul(bcdiv($result, bcadd($percentage, '100', self::SCALE), self::SCALE), '100', self::SCALE);
231
232
        return $round ? self::round($res) : $res;
233
    }
234
235
    public static function addVat($value, $percentage)
236
    {
237
        return self::addPercent($value, $percentage);
238
    }
239
240
    public static function removeVat($total, $percentage)
241
    {
242
        return self::beforePercentAddition($total, $percentage);
243
    }
244
245
    public static function vatAmount($total, $percentage): string
246
    {
247
        $withoutVat = self::beforePercentAddition($total, $percentage, false);
248
        return self::percent($withoutVat, $percentage);
249
    }
250
251
    public static function isNullOrZero($number): bool
252
    {
253
        return $number === null || self::isZero($number);
254
    }
255
256
    public static function isZero($number): bool
257
    {
258
        return (float)$number === .0;
259
    }
260
261
    /**
262
     * Performs addition to all passed arguments
263
     * NumberTool::add('1.00', '2.00', '3.00') //6.00
264
     * @param string $op1
265
     * @param string $op2
266
     * @param boolean $round
267
     * @return string
268
     */
269
    public static function addAll()
270
    {
271
        $res = '0.00';
272
        foreach (func_get_args() as $arg) {
273
            $res = self::add($res, $arg);
274
        }
275
        return $res;
276
    }
277
278
    /**
279
     * Returns true if $left is greater than $right
280
     * @param string $left left operand
281
     * @param string $right right operand
282
     * @return bool
283
     */
284
    public static function gt($left, $right): bool
285
    {
286
        return bccomp(self::f($left), self::f($right), self::SCALE) === 1;
287
    }
288
289
    /**
290
     * Returns true if $left is greater than or equal to $right
291
     * @param string $left left operand
292
     * @param string $right right operand
293
     * @return bool
294
     */
295
    public static function gte($left, $right): bool
296
    {
297
        $comp = bccomp(self::f($left), self::f($right), self::SCALE);
298
        return $comp === 0 || $comp === 1;
299
    }
300
301
    /**
302
     * Returns true if $left is smaller than $right
303
     * @param string $left left operand
304
     * @param string $right right operand
305
     * @return bool
306
     */
307
    public static function lt($left, $right): bool
308
    {
309
        return bccomp(self::f($left), self::f($right), self::SCALE) === -1;
310
    }
311
312
    /**
313
     * Returns true if $left is smaller than or equal to $right
314
     * @param mixed $left left operand
315
     * @param mixed $right right operand
316
     * @return bool
317
     */
318
    public static function lte($left, $right): bool
319
    {
320
        $comp = bccomp(self::f($left), self::f($right), self::SCALE);
321
        return $comp === 0 || $comp === -1;
322
    }
323
324
    /**
325
     * Returns true if $left is equal to $right
326
     * @param string $left left operand
327
     * @param string $right right operand
328
     * @return bool
329
     */
330
    public static function eq($left, $right): bool
331
    {
332
        return bccomp(self::f($left), self::f($right), self::SCALE) === 0;
333
    }
334
335
    /**
336
     * PHP Version of PMT in Excel.
337
     *
338
     * @param float $apr
339
     *   Interest rate.
340
     * @param integer $term
341
     *   Loan length in months.
342
     * @param float $loan
343
     *   The loan amount.
344
     *
345
     * @return float
346
     *   The monthly mortgage amount.
347
     */
348
    public static function pmt($apr, $term, $loan): float
349
    {
350
        $apr = $apr / 1200;
351
        $amount = $apr * -$loan * ((1 + $apr) ** $term) / (1 - ((1 + $apr) ** $term));
352
        return number_format($amount, 2);
0 ignored issues
show
Bug Best Practice introduced by
The expression return number_format($amount, 2) returns the type string which is incompatible with the type-hinted return double.
Loading history...
353
    }
354
355
    /**
356
     * Calculate median value by array values.
357
     *
358
     * @access public
359
     * @param array $values
360
     * @param boolean $round
361
     * @return float|string
362
     */
363
    public static function calculateMedian($values, $round = true)
364
    {
365
        $count = count($values); // total numbers in array
366
        $middleval = floor(($count - 1) / 2); // find the middle value, or the lowest middle value
367
368
        if ($count % 2) { // odd number, middle is the median
369
            $median = $values[$middleval];
370
        } else { // even number, calculate avg of 2 medians
371
            $low = $values[$middleval];
372
            $high = $values[$middleval + 1];
373
            $median = (($low + $high) / 2);
374
        }
375
376
        return $round ? self::round($median) : $median;
377
    }
378
379
    /**
380
     * Calculate average value by array values.
381
     *
382
     * @access public
383
     * @param array $values
384
     * @param boolean $round
385
     * @return float|string
386
     */
387
    public static function calculateAverage($values, $round = true)
388
    {
389
        $count = count($values); // total numbers in array
390
391
        $total = 0;
392
        foreach ($values as $value) {
393
            $total += $value; // total value of array numbers
394
        }
395
        $average = ($total / $count); // get average value
396
397
        return $round ? self::round($average) : $average;
398
    }
399
400
    public static function numberToText($number, $locale = null)
401
    {
402
        if (!$locale) {
403
            $locale = 'en';
404
        }
405
406
        $style = \NumberFormatter::SPELLOUT;
407
        $formatter = new \NumberFormatter($locale, $style);
408
409
        // Format
410
        $formatted = $formatter->format($number);
411
412
        // Remove soft hyphens
413
        $formatted = preg_replace('~\x{00AD}~u', '', $formatted);
414
415
        return $formatted;
416
    }
417
418
    /**
419
     * Returns rounded value and difference after rounding
420
     * @param mixed $value
421
     * @return array
422
     */
423
    public static function getRoundedValueAndDifference($value): array
424
    {
425
        $rounded = self::truncate($value, 2);
426
        $diff = self::sub($value, $rounded, false);
427
428
        return [
429
            'roundedValue' => $rounded,
430
            'diff' => $diff,
431
        ];
432
    }
433
434
    /**
435
     * @param float|int $total
436
     * @param float|int $partial
437
     * @return string
438
     */
439
    public static function getPercentageBetweenTwo($total, $partial): string
440
    {
441
        if (0 == $partial) {
442
            return '0';
443
        }
444
445
        return number_format(($partial / $total) * 100, 2);
446
    }
447
448
    /**
449
     * Usage:
450
     *   NumberTool::result('$1 + $2', $variable, $variableTwo)
451
     *   NumberTool::result('((1 + $1)^12 - 1) * 100', $variable)
452
     *
453
     * @param string $string
454
     * @return string
455
     */
456
    public static function result(string $string): string
457
    {
458
        bcscale(self::SCALE);
459
460
        $argv = func_get_args();
461
        $string = str_replace(' ', '', "({$string})");
462
463
        $operations = [];
464
        if (strpos($string, '^') !== false) {
465
            $operations[] = '\^';
466
        }
467
        if (strpbrk($string, '*/%') !== false) {
468
            $operations[] = '[\*\/\%]';
469
        }
470
        if (strpbrk($string, '+-') !== false) {
471
            $operations[] = '[\+\-]';
472
        }
473
        if (strpbrk($string, '<>!=') !== false) {
474
            $operations[] = '<|>|=|<=|==|>=|!=|<>';
475
        }
476
477
        $string = preg_replace_callback('/\$([0-9\.]+)/', function ($matches) use ($argv) {
478
            return $argv[$matches[1]];
479
        }, $string);
480
481
        while (preg_match('/\(([^\)\(]*)\)/', $string, $match)) {
482
            foreach ($operations as $operation) {
483
                if (preg_match("/([+-]{0,1}[0-9\.]+)($operation)([+-]{0,1}[0-9\.]+)/", $match[1], $m)) {
484
                    $result = self::getResult($m[1], $m[2], $m[3]);
485
                    $match[1] = str_replace($m[0], $result, $match[1]);
486
                }
487
            }
488
            $string = str_replace($match[0], $match[1], $string);
489
        }
490
491
        return $string;
492
    }
493
494
    private static function getResult($m1, string $operator, $m3)
495
    {
496
        $result = null;
497
        switch ($operator) {
498
            case '+':
499
                $result = bcadd($m1, $m3);
500
                break;
501
            case '-':
502
                $result = bcsub($m1, $m3);
503
                break;
504
            case '*':
505
                $result = bcmul($m1, $m3);
506
                break;
507
            case '/':
508
                $result = bcdiv($m1, $m3);
509
                break;
510
            case '%':
511
                $result = bcmod($m1, $m3);
512
                break;
513
            case '^':
514
                $result = bcpow($m1, $m3);
515
                break;
516
            case '==':
517
            case '=':
518
                $result = bccomp($m1, $m3) === 0;
519
                break;
520
            case '>':
521
                $result = bccomp($m1, $m3) === 1;
522
                break;
523
            case '<':
524
                $result = bccomp($m1, $m3) === -1;
525
                break;
526
            case '>=':
527
                $result = bccomp($m1, $m3) >= 0;
528
                break;
529
            case '<=':
530
                $result = bccomp($m1, $m3) <= 0;
531
                break;
532
            case '<>':
533
            case '!=':
534
                $result = bccomp($m1, $m3) != 0;
535
                break;
536
        }
537
        return $result;
538
    }
539
}
540