Passed
Push — master ( c50f33...e75397 )
by Jordan
04:38 queued 12s
created

RoundingProvider::round()   F

Complexity

Conditions 29
Paths > 20000

Size

Total Lines 127
Code Lines 89

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 77
CRAP Score 29.0017

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 29
eloc 89
c 1
b 0
f 0
nc 675844
nop 2
dl 0
loc 127
ccs 77
cts 78
cp 0.9872
crap 29.0017
rs 0

How to fix   Long Method    Complexity   

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:

1
<?php
2
3
namespace Samsara\Fermat\Provider;
4
5
use JetBrains\PhpStorm\ExpectedValues;
6
use JetBrains\PhpStorm\Pure;
7
use Samsara\Fermat\Enums\RandomMode;
8
use Samsara\Fermat\Enums\RoundingMode;
9
use Samsara\Fermat\Types\Base\Interfaces\Numbers\DecimalInterface;
10
11
/**
12
 *
13
 */
14
class RoundingProvider
15
{
16
17
    private static RoundingMode $mode = RoundingMode::HalfEven;
18
    private static ?DecimalInterface $decimal;
19
    private static int $alt = 1;
20
    private static ?string $remainder;
21
22
    /**
23
     * @param RoundingMode $mode
24
     * @return void
25
     */
26 26
    public static function setRoundingMode(
27
        RoundingMode $mode
28
    ): void
29
    {
30 26
        static::$mode = $mode;
0 ignored issues
show
Bug introduced by
Since $mode is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $mode to at least protected.
Loading history...
31
    }
32
33
    /**
34
     * @return RoundingMode
35
     */
36 48
    public static function getRoundingMode(): RoundingMode
37
    {
38 48
        return self::$mode;
39
    }
40
41
    /**
42
     * @param DecimalInterface $decimal
43
     * @param int $places
44
     * @return string
45
     */
46 48
    public static function round(DecimalInterface $decimal, int $places = 0): string
47
    {
48 48
        static::$decimal = $decimal;
0 ignored issues
show
Bug introduced by
Since $decimal is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $decimal to at least protected.
Loading history...
49
50 48
        $rawString = str_replace('-', '', $decimal->getAsBaseTenRealNumber());
51
52 48
        $sign = $decimal->isNegative() ? '-' : '';
53 48
        $imaginary = $decimal->isImaginary() ? 'i' : '';
54
55 48
        if ($decimal->isInt() && $places >= 0) {
56 8
            return $sign.$rawString.$imaginary;
57
        }
58
59 48
        if (str_contains($rawString, '.')) {
60 48
            [$wholePart, $decimalPart] = explode('.', $rawString);
61
        } else {
62 6
            $wholePart = $rawString;
63 6
            $decimalPart = '';
64
        }
65
66 48
        $absPlaces = abs($places);
67
68 48
        $currentPart = $places >= 0;
69 48
        $roundedPart = $currentPart ? str_split($decimalPart) : str_split($wholePart);
70 48
        $roundedPartString = $currentPart ? $decimalPart : $wholePart;
71 48
        $otherPart = $currentPart ? str_split($wholePart) : str_split($decimalPart);
72 48
        $baseLength = $currentPart ? strlen($decimalPart)-1 : strlen($wholePart);
73 48
        $pos = $currentPart ? $places : $baseLength + $places;
74 48
        $carry = 0;
75
76 48
        if ($currentPart) {
77 48
            $pos = ($absPlaces > $baseLength) ? $baseLength : $pos;
78
        } else {
79 6
            $pos = ($absPlaces >= $baseLength) ? 0 : $pos;
80
        }
81
82
        do {
83 48
            if (!array_key_exists($pos, $roundedPart)) {
0 ignored issues
show
Bug introduced by
It seems like $roundedPart can also be of type true; however, parameter $array of array_key_exists() does only seem to accept ArrayObject|array, 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

83
            if (!array_key_exists($pos, /** @scrutinizer ignore-type */ $roundedPart)) {
Loading history...
84
                break;
85
            }
86
87 48
            $digit = (int)$roundedPart[$pos] + $carry;
88
89 48
            if ($carry == 0 && $digit == 5) {
90 26
                static::$remainder = substr($roundedPartString, $pos+1);
0 ignored issues
show
Bug introduced by
Since $remainder is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $remainder to at least protected.
Loading history...
91
            } else {
92 46
                static::$remainder = null;
93
            }
94
95 48
            if ($pos == 0) {
96 29
                if ($currentPart) {
97 29
                    $nextDigit = (int)$otherPart[count($otherPart)-1];
0 ignored issues
show
Bug introduced by
It seems like $otherPart can also be of type true; however, parameter $value of count() does only seem to accept Countable|array, 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

97
                    $nextDigit = (int)$otherPart[count(/** @scrutinizer ignore-type */ $otherPart)-1];
Loading history...
98
                } else {
99 29
                    $nextDigit = 0;
100
                }
101
            } else {
102 37
                $nextDigit = (int)$roundedPart[$pos-1];
103
            }
104
105 48
            if ($carry == 0) {
106 48
                $carry = match (self::getRoundingMode()) {
107 48
                    RoundingMode::HalfUp => self::roundHalfUp($digit),
108 47
                    RoundingMode::HalfDown => self::roundHalfDown($digit),
109 46
                    RoundingMode::HalfOdd => self::roundHalfOdd($digit, $nextDigit),
110 45
                    RoundingMode::HalfZero => self::roundHalfZero($digit),
111 44
                    RoundingMode::HalfInf => self::roundHalfInf($digit),
112 43
                    RoundingMode::Ceil => self::roundCeil($digit),
113 43
                    RoundingMode::Floor => self::roundFloor(),
114 35
                    RoundingMode::HalfRandom => self::roundRandom($digit),
115 34
                    RoundingMode::HalfAlternating => self::roundAlternating($digit),
116 33
                    RoundingMode::Stochastic => self::roundStochastic($digit),
117 32
                    default => self::roundHalfEven($digit, $nextDigit)
118
                };
119
            } else {
120 40
                if ($digit > 9) {
121 13
                    $carry = 1;
122 13
                    $roundedPart[$pos] = '0';
123
                } else {
124 40
                    $carry = 0;
125 40
                    $roundedPart[$pos] = $digit;
126
                }
127
            }
128
129 48
            if ($pos == 0 && $carry == 1) {
130 16
                if ($currentPart) {
131 16
                    $currentPart = false;
132
133
                    // Do the variable swap dance
134 16
                    $temp = $otherPart;
135 16
                    $otherPart = $roundedPart;
136 16
                    $roundedPart = $temp;
137
138 16
                    $pos = count($roundedPart)-1;
139
                } else {
140 7
                    array_unshift($roundedPart, $carry);
0 ignored issues
show
Bug introduced by
It seems like $roundedPart can also be of type true; however, parameter $array of array_unshift() does only seem to accept array, 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
                    array_unshift(/** @scrutinizer ignore-type */ $roundedPart, $carry);
Loading history...
141 16
                    $carry = 0;
142
                }
143
            } else {
144 48
                $pos -= 1;
145
            }
146 48
        } while ($carry == 1);
147
148 48
        if ($currentPart) {
149 47
            $newDecimalPart = implode('', $roundedPart);
150 47
            $newWholePart = implode('', $otherPart);
0 ignored issues
show
Bug introduced by
It seems like $otherPart can also be of type true; however, parameter $pieces of implode() does only seem to accept array, 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

150
            $newWholePart = implode('', /** @scrutinizer ignore-type */ $otherPart);
Loading history...
151
        } else {
152 16
            $newDecimalPart = implode('', $otherPart);
153 16
            $newWholePart = implode('', $roundedPart);
154
        }
155
156 48
        if ($places > 0) {
157 37
            $newDecimalPart = substr($newDecimalPart, 0, $places);
158 27
        } elseif ($places == 0) {
159 27
            $newDecimalPart = '0';
160
        } else {
161 6
            $newWholePart = substr($newWholePart, 0, strlen($wholePart)+$places).str_repeat('0', $places*-1);
162 6
            $newDecimalPart = '0';
163
        }
164
165 48
        if (!strlen(str_replace('0', '', $newDecimalPart))) {
166 30
            $newDecimalPart = '0';
167
        }
168
169 48
        static::$remainder = null;
170 48
        static::$decimal = null;
171
172 48
        return $sign.$newWholePart.'.'.$newDecimalPart.$imaginary;
173
    }
174
175 35
    #[Pure]
176
    private static function nonHalfEarlyReturn(int $digit): int
177
    {
178 35
        return $digit <=> 5;
179
    }
180
181 2
    private static function negativeReverser(): int
182
    {
183 2
        if (static::$decimal->isNegative()) {
0 ignored issues
show
Bug introduced by
Since $decimal is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $decimal to at least protected.
Loading history...
Bug introduced by
The method isNegative() 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

183
        if (static::$decimal->/** @scrutinizer ignore-call */ isNegative()) {

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...
184 2
            return 1;
185
        } else {
186 2
            return 0;
187
        }
188
    }
189
190 38
    private static function remainderCheck(): bool
191
    {
192 38
        $remainder = static::$remainder;
0 ignored issues
show
Bug introduced by
Since $remainder is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $remainder to at least protected.
Loading history...
193
194 38
        if (is_null($remainder)) {
195 35
            return false;
196
        }
197
198 21
        $remainder = str_replace('0', '', $remainder);
199
200 21
        return !empty($remainder);
201
    }
202
203 1
    private static function roundHalfUp(int $digit): int
204
    {
205 1
        $negative = self::negativeReverser();
206 1
        $remainder = self::remainderCheck();
207
208 1
        if ($negative) {
209 1
            return $digit > 5 || ($digit == 5 && $remainder) ? 1 : 0;
210
        } else {
211 1
            return $digit > 4 ? 1 : 0;
212
        }
213
    }
214
215 1
    private static function roundHalfDown(int $digit): int
216
    {
217 1
        $negative = self::negativeReverser();
218 1
        $remainder = self::remainderCheck();
219
220 1
        if ($negative) {
221 1
            return $digit > 4 ? 1 : 0;
222
        } else {
223 1
            return $digit > 5 || ($digit == 5 && $remainder) ? 1 : 0;
224
        }
225
    }
226
227 32
    private static function roundHalfEven(int $digit, int $nextDigit): int
228
    {
229 32
        $early = static::nonHalfEarlyReturn($digit);
230 32
        $remainder = self::remainderCheck();
231
232 32
        if ($early == 0) {
233 15
            return ($nextDigit % 2 == 0 && !$remainder) ? 0 : 1;
234
        } else {
235 31
            return $early == 1 ? 1 : 0;
236
        }
237
    }
238
239 1
    private static function roundHalfOdd(int $digit, int $nextDigit): int
240
    {
241 1
        $early = static::nonHalfEarlyReturn($digit);
242 1
        $remainder = self::remainderCheck();
243
244 1
        if ($early == 0) {
245 1
            return ($nextDigit % 2 == 1 && !$remainder) ? 0 : 1;
246
        } else {
247 1
            return $early == 1 ? 1 : 0;
248
        }
249
    }
250
251 1
    private static function roundHalfZero(int $digit): int
252
    {
253 1
        $remainder = self::remainderCheck();
254
255 1
        return $digit > 5 || ($digit == 5 && $remainder) ? 1 : 0;
256
    }
257
258 1
    #[Pure]
259
    private static function roundHalfInf(int $digit): int
260
    {
261 1
        return $digit > 4 ? 1 : 0;
262
    }
263
264 4
    #[Pure]
265
    private static function roundCeil(int $digit): int
266
    {
267 4
        return $digit == 0 ? 0 : 1;
268
    }
269
270 17
    #[Pure]
271
    private static function roundFloor(): int
272
    {
273 17
        return 0;
274
    }
275
276 1
    private static function roundRandom(int $digit): int
277
    {
278 1
        $early = static::nonHalfEarlyReturn($digit);
279 1
        $remainder = self::remainderCheck();
280
281 1
        if ($early == 0 && !$remainder) {
282 1
            return RandomProvider::randomInt(0, 1, RandomMode::Speed)->asInt();
283
        } else {
284
            return (($early == 1 || $remainder) ? 1 : 0);
285
        }
286
    }
287
288 1
    private static function roundAlternating(int $digit): int
289
    {
290 1
        $early = static::nonHalfEarlyReturn($digit);
291 1
        $remainder = self::remainderCheck();
292
293 1
        if ($early == 0 && !$remainder) {
294 1
            $val = self::$alt;
295 1
            self::$alt = (int)!$val;
296
297 1
            return $val;
298
        } else {
299
            return (($early == 1 || $remainder) ? 1 : 0);
300
        }
301
    }
302
303 1
    private static function roundStochastic(int $digit): int
304
    {
305 1
        $remainder = static::$remainder;
0 ignored issues
show
Bug introduced by
Since $remainder is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $remainder to at least protected.
Loading history...
306
307 1
        if (is_null($remainder)) {
308
            $target = $digit;
309
            $rangeMin = 0;
310
            $rangeMax = 9;
311
        } else {
312 1
            $remainder = substr($remainder, 0, 3);
313 1
            $target = (int)($digit.$remainder);
314 1
            $rangeMin = 0;
315 1
            $rangeMax = (int)str_repeat('9', strlen($remainder) + 1);
316
        }
317
318 1
        $random = RandomProvider::randomInt($rangeMin, $rangeMax, RandomMode::Speed)->asInt();
319
320 1
        if ($random < $target) {
321 1
            return 1;
322
        } else {
323 1
            return 0;
324
        }
325
    }
326
327
}