Failed Conditions
Push — master ( 76ac40...0cc284 )
by Roman
01:42
created

ExcelGoalSeek::calculate()   D

Complexity

Conditions 26
Paths 36

Size

Total Lines 113
Code Lines 49

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 48
CRAP Score 26.3074

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 49
c 2
b 0
f 0
dl 0
loc 113
ccs 48
cts 52
cp 0.9231
rs 4.1666
cc 26
nc 36
nop 11
crap 26.3074

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace P4lv\ExcelGoalSeek;
4
5
use P4lv\ExcelGoalSeek\Exception\ExcelGoalSeekException;
6
use P4lv\ExcelGoalSeek\Exception\GoalNeverReached;
7
use P4lv\ExcelGoalSeek\Exception\GoalReachedNotEnough;
8
use Psr\Log\LoggerInterface;
9
10
class ExcelGoalSeek
11
{
12
    private $debugEnabled;
13
    /**
14
     * @var LoggerInterface|null
15
     */
16
    private $logger;
17
18 10
    public function __construct(LoggerInterface $logger = null)
19
    {
20 10
        $this->logger = $logger;
21 10
    }
22 10
23
    private function debug($message, array $context = []): void
24 9
    {
25
        if ($this->debugEnabled instanceof LoggerInterface) {
26 9
            $this->logger->debug($message, $context);
0 ignored issues
show
Bug introduced by
The method debug() does not exist on null. ( Ignorable by Annotation )

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

26
            $this->logger->/** @scrutinizer ignore-call */ 
27
                           debug($message, $context);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
27 4
        }
28
    }
29 9
30
    public function calculate(
31 10
        $functionGS,
32
        $goal,
33
        $decimal_places,
34
        $incremental_modifier = 1,
35
        $max_loops_round = 0,
36
        $max_loops_dec = 0,
37
        $lock_min = ['num' => null, 'goal' => null],
38
        $lock_max = ['num' => null, 'goal' => null],
39
        $slope = null,
40
        $randomized = false,
41
        $start_from = 0.1
42
    )
43
    {
44
        if (empty($functionGS)) {
45 10
            throw new ExcelGoalSeekException('Function callback expected');
46 1
        }
47
48
        $max_loops_round++;
49 9
50
        //If goal found has more than this difference, return null as it is not found
51 9
        $maximum_acceptable_difference = 0.1;
52
53 9
        if ($max_loops_round > 100) {
54 1
            throw new GoalNeverReached();
55
        }
56 1
57
        $this->debug('Iteration ' . $max_loops_round . '; min value = ' . $lock_min['num'] . '; max value = ' . $lock_max['num'] . '; slope ' . $slope);
58
59 9
        //If I have the goal  limited to a unit, I seek decimals
60
        if ($lock_min['num'] !== null && $lock_max['num'] !== null && abs(abs($lock_max['num']) - abs($lock_min['num'])) <= 1) {
61
62 9
            //No decimal , return result
63
            if ($lock_min['num'] == $lock_max['num']) {
64
                return $lock_min['num'];
65 8
            }
66 2
67
            //Seek decimals
68
            foreach (range(1, $decimal_places, 1) as $decimal) {
69
                $decimal_step = 1 / (10 ** $decimal);
70 6
71 6
                $difference = abs(round(abs($lock_max['num']), $decimal) - round(abs($lock_min['num']), $decimal));
72
73 6
                while ($difference - ($decimal_step / 10) > $decimal_step && $max_loops_dec < (2000 * $decimal_places)) {
74
                    $max_loops_dec++;
75 6
76 6
                    $aux_obj_num = round(($lock_min['num'] + $lock_max['num']) / 2, $decimal);
77
                    $aux_obj = $this->$functionGS($aux_obj_num);
78 6
79 6
                    $this->debug(
80
                        'Decimal iteration ' . $max_loops_dec . '; min value = ' . $lock_min['num'] . '; max value = ' . $lock_max['num'] . '; value ' . $aux_obj
81 6
                    );
82 6
83
                    //Like when I look without decimals
84
                    [$lock_min, $lock_max] = $this->lookWithoutDecimals($aux_obj, $goal, $aux_obj_num, $lock_min, $lock_max, $slope);
85
                    //End Like when I look without decimals
86 6
                    $difference = abs(round(abs($lock_max['num']), $decimal) - round(abs($lock_min['num']), $decimal));
87
                }//End while
88 6
            }//End foreach
89
90
91
            if ($max_loops_dec > 2000 * $decimal_places) {
92
                throw new GoalNeverReached('Goal never reached [2000]');
93 6
            }
94
95
            if (!is_nan($lock_min['goal']) && abs(abs($lock_min['goal']) - abs($goal)) < $maximum_acceptable_difference) {
96
                return round($lock_min['num'], $decimal_places - 1);
97 6
            }
98 6
99
            throw new GoalReachedNotEnough('Goal reached not enough');
100
        }
101
102
        //First iteration, try with zero
103
        $aux_obj_num = $this->getAux_obj_num($lock_min['num'], $lock_max['num'], $start_from, $incremental_modifier);
104
105 9
        $aux_obj = $this->$functionGS($aux_obj_num);
106 9
107
        $this->debug('Testing (with initial value) '. ($aux_obj_num != $start_from ? '' : '(with initial value)') . $aux_obj_num . ' with value ' . $aux_obj);
108 9
109 8
        if ($slope === null) {
110 8
            $aux_slope = $this->$functionGS($aux_obj_num + 0.1);
111
112 8
            if (is_nan($aux_slope) || is_nan($aux_obj)) {
113
                $slope = null; //If slope is null
114
            } elseif ($aux_slope - $aux_obj > 0) {
115 9
                $slope = 1;
116 1
            } else {
117 1
                $slope = -1;
118
            }
119 1
        }
120
121
        //Test if formule can give me non valid values, i.e.: sqrt of negative value
122 8
        if (!is_nan($aux_obj)) {
123 8
            //Is goal without decimals?
124
            list($lock_min, $lock_max) = $this->lookWithoutDecimals($aux_obj, $goal, $aux_obj_num, $lock_min, $lock_max, $slope);
125
        } else {
126 9
            if (($lock_min['num'] === null && $lock_max['num'] === null) || $randomized) {
127 9
                $nuevo_start_from = random_int(-500, 500);
128 9
129
                return $this->calculate($functionGS, $goal, $decimal_places, $incremental_modifier + 1, $max_loops_round, $max_loops_dec, $lock_min, $lock_max, $slope, true, $nuevo_start_from);
130 9
            } //First iteration is null
131 9
132
            if ($lock_min['num'] !== null && abs(abs($aux_obj_num) - abs($lock_min['num'])) < 1) {
133
                $lock_max['num'] = $aux_obj_num;
134 9
            }
135 9
            if ($lock_max['num'] !== null && abs(abs($aux_obj_num) - abs($lock_max['num'])) < 1) {
136
                $lock_min['num'] = $aux_obj_num;
137 9
            }
138
139 9
            return $this->calculate($functionGS, $goal, $decimal_places, $incremental_modifier + 1, $max_loops_round, $max_loops_dec, $lock_min, $lock_max, $slope, $randomized, $start_from);
140 9
        }
141
142
        return $this->calculate($functionGS, $goal, $decimal_places, $incremental_modifier, $max_loops_round, $max_loops_dec, $lock_min, $lock_max, $slope, $randomized, $start_from);
143
    }
144
145
    private function lookWithoutDecimals($aux_obj, $goal, $aux_obj_num, $lock_min, $lock_max, $slope): array
146
    {
147 9
        if ($aux_obj == $goal) {
148
            $lock_min['num'] = $aux_obj_num;
149 9
            $lock_min['goal'] = $aux_obj;
150
151
            $lock_max['num'] = $aux_obj_num;
152
            $lock_max['goal'] = $aux_obj;
153
        }
154
155
        $going_up = false;
156
        if ($aux_obj < $goal) {
157
            $going_up = true;
158
        }
159
        if ($aux_obj > $goal) {
160
            $going_up = false;
161
        }
162
        if ($slope == -1) {
163
            $going_up = !$going_up;
164
        }
165
166
        if ($going_up) {
167 9
            if ($lock_min['num'] !== null && $aux_obj_num < $lock_min['num']) {
168
                $lock_max['num'] = $lock_min['num'];
169
                $lock_max['goal'] = $lock_min['goal'];
170 9
            }
171
172 9
            $lock_min['num'] = $aux_obj_num;
173 8
            $lock_min['goal'] = $aux_obj;
174 8
        }
175
176 8
        if (!$going_up) {
177 8
            if ($lock_max['num'] !== null && $lock_max['num'] < $aux_obj_num) {
178
                $lock_min['num'] = $lock_max['num'];
179
                $lock_min['goal'] = $lock_max['goal'];
180 9
            }
181 9
182 8
            $lock_max['num'] = $aux_obj_num;
183
            $lock_max['goal'] = $aux_obj;
184 9
        }
185 9
        return [$lock_min, $lock_max];
186
    }
187 9
188
    /**
189
     * @param $lockMinNum
190
     * @param $lockMaxNum
191 9
     * @param $start_from
192 8
     * @param $incremental_modifier
193
     * @return float|int|mixed
194
     */
195
    private function getAux_obj_num($lockMinNum, $lockMaxNum, $start_from, $incremental_modifier)
196
    {
197 8
        $aux_obj_num = null;
198 8
        if ($lockMinNum === null && $lockMaxNum === null) {
199
            $aux_obj_num = $start_from;
200
        } //Lower limit found, searching higher limit with * 10
201 9
        elseif ($lockMinNum !== null && $lockMaxNum === null) {
202 9
            if ($lockMinNum == $start_from) {
203
                $aux_obj_num = 1;
204
            } else {
205
                $aux_obj_num = $lockMinNum * (10 / $incremental_modifier);
206
            }
207 9
        } //Higher limit found, searching lower limit with * -10
208 9
        elseif ($lockMinNum === null && $lockMaxNum !== null) {
209
            if ($lockMaxNum == $start_from) {
210 9
                $aux_obj_num = -1;
211
            } else {
212
                $aux_obj_num = $lockMaxNum * (10 / $incremental_modifier);
213
            }
214
        } //I have both limits, searching between them without decimals
215
        elseif ($lockMinNum !== null && $lockMaxNum !== null) {
216
            $aux_obj_num = round(($lockMinNum + $lockMaxNum) / 2);
217
        }
218
        return $aux_obj_num;
219
    }
220
}
221