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
|
|||
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 |
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.