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

src/ExcelGoalSeek/ExcelGoalSeek.php (1 issue)

Labels
Severity
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
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