Completed
Push — master ( 2683bd...cf87f7 )
by Jordan
21s queued 13s
created

RoundingProvider::roundPreFormat()   D

Complexity

Conditions 11
Paths 384

Size

Total Lines 40
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 11

Importance

Changes 0
Metric Value
cc 11
eloc 29
nc 384
nop 2
dl 0
loc 40
ccs 19
cts 19
cp 1
crap 11
rs 4.1833
c 0
b 0
f 0

How to fix   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\Provider\RoundingModeAdapters\ModeAdapterFactory;
10
use Samsara\Fermat\Types\Base\Interfaces\Numbers\DecimalInterface;
11
12
/**
13
 *
14
 */
15
class RoundingProvider
16
{
17
18
    private static RoundingMode $mode = RoundingMode::HalfEven;
19
20
    /**
21
     * @param RoundingMode $mode
22
     * @return void
23
     */
24 447
    public static function setRoundingMode(
25
        RoundingMode $mode
26
    ): void
27
    {
28 447
        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...
29
    }
30
31
    /**
32
     * @return RoundingMode
33
     */
34 860
    public static function getRoundingMode(): RoundingMode
35
    {
36 860
        return self::$mode;
37
    }
38
39
    /**
40
     * @param string $decimal
41
     * @param int $places
42
     * @return string
43
     */
44 1534
    public static function round(string $decimal, int $places = 0): string
45
    {
46 1534
        $carry = 0;
47
48
        [
49
            $rawString,
50
            $roundedPart,
51
            $roundedPartString,
52
            $otherPart,
53
            $pos,
54
            $wholePart,
55
            $decimalPart,
56
            $currentPart,
57
            $isNegative
58 1534
        ] = self::roundPreFormat($decimal, $places);
59
60 1534
        $sign = $isNegative ? '-' : '';
61 1534
        $imaginary = str_ends_with($decimal, 'i') ? 'i' : '';
62
63 1534
        if (empty($decimalPart) && $places >= 0) {
64 921
            return $sign.$rawString.$imaginary;
65
        }
66
67
        do {
68 1053
            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

68
            if (!array_key_exists($pos, /** @scrutinizer ignore-type */ $roundedPart)) {
Loading history...
69 624
                break;
70
            }
71
72 860
            [$digit, $nextDigit, $remainder] = self::roundLoopStart(
73
                $roundedPart,
0 ignored issues
show
Bug introduced by
It seems like $roundedPart can also be of type true; however, parameter $roundedPart of Samsara\Fermat\Provider\...vider::roundLoopStart() 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

73
                /** @scrutinizer ignore-type */ $roundedPart,
Loading history...
74
                $otherPart,
0 ignored issues
show
Bug introduced by
It seems like $otherPart can also be of type true; however, parameter $otherPart of Samsara\Fermat\Provider\...vider::roundLoopStart() 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

74
                /** @scrutinizer ignore-type */ $otherPart,
Loading history...
75
                $roundedPartString,
76
                $pos,
77
                $carry,
78
                $currentPart
79
            );
80
81 860
            if ($carry == 0) {
82 860
                $roundingMode = ModeAdapterFactory::getAdapter(self::getRoundingMode(), $isNegative, $remainder);
83 860
                $carry = $roundingMode->determineCarry($digit, $nextDigit);
84
            } else {
85 697
                if ($digit > 9) {
86 332
                    $carry = 1;
87 332
                    $roundedPart[$pos] = '0';
88
                } else {
89 670
                    $carry = 0;
90 670
                    $roundedPart[$pos] = $digit;
91
                }
92
            }
93
94 860
            [$roundedPart, $otherPart, $pos, $carry, $currentPart] = self::roundLoopEnd(
95
                $roundedPart,
0 ignored issues
show
Bug introduced by
It seems like $roundedPart can also be of type true; however, parameter $roundedPart of Samsara\Fermat\Provider\...rovider::roundLoopEnd() 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

95
                /** @scrutinizer ignore-type */ $roundedPart,
Loading history...
96
                $otherPart,
0 ignored issues
show
Bug introduced by
It seems like $otherPart can also be of type true; however, parameter $otherPart of Samsara\Fermat\Provider\...rovider::roundLoopEnd() 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

96
                /** @scrutinizer ignore-type */ $otherPart,
Loading history...
97
                $pos,
98
                $carry,
99
                $currentPart
100
            );
101 860
        } while ($carry == 1);
102
103 1053
        [$newWholePart, $newDecimalPart] = self::roundPostFormat($currentPart, $wholePart, $roundedPart, $otherPart, $places);
0 ignored issues
show
Bug introduced by
$currentPart of type boolean is incompatible with the type string expected by parameter $currentPart of Samsara\Fermat\Provider\...ider::roundPostFormat(). ( Ignorable by Annotation )

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

103
        [$newWholePart, $newDecimalPart] = self::roundPostFormat(/** @scrutinizer ignore-type */ $currentPart, $wholePart, $roundedPart, $otherPart, $places);
Loading history...
Bug introduced by
It seems like $otherPart can also be of type true; however, parameter $otherPart of Samsara\Fermat\Provider\...ider::roundPostFormat() 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

103
        [$newWholePart, $newDecimalPart] = self::roundPostFormat($currentPart, $wholePart, $roundedPart, /** @scrutinizer ignore-type */ $otherPart, $places);
Loading history...
104
105 1053
        return $sign.$newWholePart.'.'.$newDecimalPart.$imaginary;
106
    }
107
108
    /**
109
     * @param string $decimal
110
     * @param int $places
111
     * @return array
112
     */
113 1534
    private static function roundPreFormat(string $decimal, int $places): array
114
    {
115 1534
        $decimal = trim(rtrim($decimal));
116
117 1534
        $isNegative = str_starts_with($decimal, '-');
118
119 1534
        $rawString = str_replace('-', '', $decimal);
120
121 1534
        if (str_contains($rawString, '.')) {
122 1041
            [$wholePart, $decimalPart] = explode('.', $rawString);
123
        } else {
124 933
            $wholePart = $rawString;
125 933
            $decimalPart = '';
126
        }
127
128 1534
        $absPlaces = abs($places);
129
130 1534
        $currentPart = $places >= 0;
131 1534
        $roundedPart = $currentPart ? str_split($decimalPart) : str_split($wholePart);
132 1534
        $roundedPartString = $currentPart ? $decimalPart : $wholePart;
133 1534
        $otherPart = $currentPart ? str_split($wholePart) : str_split($decimalPart);
134 1534
        $baseLength = $currentPart ? strlen($decimalPart)-1 : strlen($wholePart);
135 1534
        $pos = $currentPart ? $places : $baseLength + $places;
136
137 1534
        if ($currentPart) {
138 1522
            $pos = ($absPlaces > $baseLength && $places < 0) ? $baseLength : $pos;
139
        } else {
140 12
            $pos = ($absPlaces >= $baseLength) ? 0 : $pos;
141
        }
142
143
        return [
144 1534
            $rawString,
145
            $roundedPart,
146
            $roundedPartString,
147
            $otherPart,
148
            $pos,
149
            $wholePart,
150
            $decimalPart,
151
            $currentPart,
152
            $isNegative
153
        ];
154
    }
155
156
    /**
157
     * @param string $currentPart
158
     * @param string $wholePart
159
     * @param array $roundedPart
160
     * @param array $otherPart
161
     * @param int $places
162
     * @return array
163
     */
164 1053
    private static function roundPostFormat(
165
        string $currentPart,
166
        string $wholePart,
167
        array $roundedPart,
168
        array $otherPart,
169
        int $places
170
    ): array
171
    {
172 1053
        if ($currentPart) {
173 984
            $newDecimalPart = implode('', $roundedPart);
174 984
            $newWholePart = implode('', $otherPart);
175
        } else {
176 369
            $newDecimalPart = implode('', $otherPart);
177 369
            $newWholePart = implode('', $roundedPart);
178
        }
179
180 1053
        if ($places > 0) {
181 989
            $newDecimalPart = substr($newDecimalPart, 0, $places);
182 379
        } elseif ($places == 0) {
183 367
            $newDecimalPart = '0';
184
        } else {
185 12
            $newWholePart = substr($newWholePart, 0, strlen($wholePart)+$places).str_repeat('0', $places*-1);
186 12
            $newDecimalPart = '0';
187
        }
188
189 1053
        if (!strlen(str_replace('0', '', $newDecimalPart))) {
190 442
            $newDecimalPart = '0';
191
        }
192
193 1053
        return [$newWholePart, $newDecimalPart];
194
    }
195
196
    /**
197
     * @param array $roundedPart
198
     * @param array $otherPart
199
     * @param string $roundedPartString
200
     * @param int $pos
201
     * @param int $carry
202
     * @param bool $currentPart
203
     * @return array
204
     */
205 860
    private static function roundLoopStart(
206
        array $roundedPart,
207
        array $otherPart,
208
        string $roundedPartString,
209
        int $pos,
210
        int $carry,
211
        bool $currentPart
212
    ): array
213
    {
214 860
        $digit = (int)$roundedPart[$pos] + $carry;
215
216 860
        if ($carry == 0 && $digit == 5 && strlen($roundedPartString) > $pos+1) {
217 228
            $remainder = substr($roundedPartString, $pos+1);
218
        } else {
219 860
            $remainder = null;
220
        }
221
222 860
        if ($pos == 0) {
223 395
            if ($currentPart) {
224 395
                $nextDigit = (int)$otherPart[count($otherPart)-1];
225
            } else {
226 395
                $nextDigit = 0;
227
            }
228
        } else {
229 816
            $nextDigit = (int)$roundedPart[$pos-1];
230
        }
231
232 860
        return [$digit, $nextDigit, $remainder];
233
    }
234
235
    /**
236
     * @param array $roundedPart
237
     * @param array $otherPart
238
     * @param int $pos
239
     * @param int $carry
240
     * @param bool $currentPart
241
     * @return array
242
     */
243 860
    private static function roundLoopEnd(
244
        array $roundedPart,
245
        array $otherPart,
246
        int $pos,
247
        int $carry,
248
        bool $currentPart
249
    ): array
250
    {
251 860
        if ($pos == 0 && $carry == 1) {
252 357
            if ($currentPart) {
253 357
                $currentPart = false;
254
255
                // Do the variable swap dance
256 357
                $temp = $otherPart;
257 357
                $otherPart = $roundedPart;
258 357
                $roundedPart = $temp;
259
260 357
                $pos = count($roundedPart)-1;
261
            } else {
262 27
                array_unshift($roundedPart, $carry);
263 357
                $carry = 0;
264
            }
265
        } else {
266 860
            $pos -= 1;
267
        }
268
269 860
        return [$roundedPart, $otherPart, $pos, $carry, $currentPart];
270
    }
271
272
}