RoundingProvider::getRoundingMode()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
eloc 1
nc 1
nop 0
1
<?php
2
3
namespace Samsara\Fermat\Core\Provider;
4
5
use JetBrains\PhpStorm\ExpectedValues;
6
use JetBrains\PhpStorm\Pure;
7
use Samsara\Fermat\Core\Enums\RandomMode;
8
use Samsara\Fermat\Core\Enums\RoundingMode;
9
use Samsara\Fermat\Core\Provider\RoundingModeAdapters\ModeAdapterFactory;
10
use Samsara\Fermat\Core\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 1286
    public static function setRoundingMode(
25
        RoundingMode $mode
26
    ): void
27
    {
28 1286
        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 2970
    public static function getRoundingMode(): RoundingMode
35
    {
36 2970
        return self::$mode;
37
    }
38
39
    /**
40
     * @param string $decimal
41
     * @param int $places
42
     * @return string
43
     */
44 4191
    public static function round(string $decimal, int $places = 0): string
45
    {
46 4191
        $carry = 0;
47
48
        [
49
            $rawString,
50
            $roundedPart,
51
            $roundedPartString,
52
            $otherPart,
53
            $pos,
54
            $wholePart,
55
            $decimalPart,
56
            $currentPart,
57
            $isNegative
58 4191
        ] = self::roundPreFormat($decimal, $places);
59
60 4191
        $sign = $isNegative ? '-' : '';
61 4191
        $imaginary = str_ends_with($decimal, 'i') ? 'i' : '';
62
63 4191
        if (empty($decimalPart) && $places >= 0) {
64 2602
            return $sign.$rawString.$imaginary;
65
        }
66
67
        do {
68 3309
            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 2138
                break;
70
            }
71
72 2970
            [$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\Core\Prov...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\Core\Prov...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 2970
            if ($carry == 0) {
82 2970
                $roundingMode = ModeAdapterFactory::getAdapter(self::getRoundingMode(), $isNegative, $remainder);
83 2970
                $carry = $roundingMode->determineCarry($digit, $nextDigit);
84
            } else {
85 2450
                if ($digit > 9) {
86 1251
                    $carry = 1;
87 1251
                    $roundedPart[$pos] = '0';
88
                } else {
89 2390
                    $carry = 0;
90 2390
                    $roundedPart[$pos] = $digit;
91
                }
92
            }
93
94 2970
            [$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\Core\Prov...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\Core\Prov...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 2970
        } while ($carry == 1);
102
103 3309
        [$newWholePart, $newDecimalPart] = self::roundPostFormat($currentPart, $wholePart, $roundedPart, $otherPart, $places);
0 ignored issues
show
Bug introduced by
It seems like $otherPart can also be of type true; however, parameter $otherPart of Samsara\Fermat\Core\Prov...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...
Bug introduced by
$currentPart of type boolean is incompatible with the type string expected by parameter $currentPart of Samsara\Fermat\Core\Prov...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...
104
105 3309
        return $sign.$newWholePart.'.'.$newDecimalPart.$imaginary;
106
    }
107
108
    /**
109
     * @param string $decimal
110
     * @param int $places
111
     * @return array
112
     */
113 4191
    private static function roundPreFormat(string $decimal, int $places): array
114
    {
115 4191
        $decimal = trim(rtrim($decimal));
116
117 4191
        $isNegative = str_starts_with($decimal, '-');
118
119 4191
        $rawString = str_replace('-', '', $decimal);
120
121 4191
        if (str_contains($rawString, '.')) {
122 3273
            [$wholePart, $decimalPart] = explode('.', $rawString);
123
        } else {
124 2638
            $wholePart = $rawString;
125 2638
            $decimalPart = '';
126
        }
127
128 4191
        $absPlaces = abs($places);
129
130 4191
        $currentPart = $places >= 0;
131
132 4191
        if ($currentPart) {
133 4155
            $roundedPart = str_split($decimalPart);
134 4155
            $roundedPartString = $decimalPart;
135 4155
            $otherPart = str_split($wholePart);
136 4155
            $baseLength = strlen($decimalPart)-1;
137 4155
            $pos = ($absPlaces > $baseLength && $places < 0) ? $baseLength : $places;
138
        } else {
139 36
            $roundedPart = str_split($wholePart);
140 36
            $roundedPartString = $wholePart;
141 36
            $otherPart = str_split($decimalPart);
142 36
            $baseLength = strlen($wholePart);
143 36
            $pos = ($absPlaces >= $baseLength) ? 0 : $baseLength + $places;
144
        }
145
146
        return [
147 4191
            $rawString,
148
            $roundedPart,
149
            $roundedPartString,
150
            $otherPart,
151
            $pos,
152
            $wholePart,
153
            $decimalPart,
154
            $currentPart,
155
            $isNegative
156
        ];
157
    }
158
159
    /**
160
     * @param string $currentPart
161
     * @param string $wholePart
162
     * @param array $roundedPart
163
     * @param array $otherPart
164
     * @param int $places
165
     * @return array
166
     */
167 3309
    private static function roundPostFormat(
168
        string $currentPart,
169
        string $wholePart,
170
        array $roundedPart,
171
        array $otherPart,
172
        int $places
173
    ): array
174
    {
175 3309
        if ($currentPart) {
176 3141
            $newDecimalPart = implode('', $roundedPart);
177 3141
            $newWholePart = implode('', $otherPart);
178
        } else {
179 1019
            $newDecimalPart = implode('', $otherPart);
180 1019
            $newWholePart = implode('', $roundedPart);
181
        }
182
183 3309
        if ($places > 0) {
184 3121
            $newDecimalPart = substr($newDecimalPart, 0, $places);
185 1090
        } elseif ($places == 0) {
186 1054
            $newDecimalPart = '0';
187
        } else {
188 36
            $newWholePart = substr($newWholePart, 0, strlen($wholePart)+$places).str_repeat('0', $places*-1);
189 36
            $newDecimalPart = '0';
190
        }
191
192 3309
        if (!strlen(str_replace('0', '', $newDecimalPart))) {
193 1348
            $newDecimalPart = '0';
194
        }
195
196 3309
        return [$newWholePart, $newDecimalPart];
197
    }
198
199
    /**
200
     * @param array $roundedPart
201
     * @param array $otherPart
202
     * @param string $roundedPartString
203
     * @param int $pos
204
     * @param int $carry
205
     * @param bool $currentPart
206
     * @return array
207
     */
208 2970
    private static function roundLoopStart(
209
        array $roundedPart,
210
        array $otherPart,
211
        string $roundedPartString,
212
        int $pos,
213
        int $carry,
214
        bool $currentPart
215
    ): array
216
    {
217 2970
        $digit = (int)$roundedPart[$pos] + $carry;
218
219 2970
        if ($carry == 0 && $digit == 5 && strlen($roundedPartString) > $pos+1) {
220 1093
            $remainder = substr($roundedPartString, $pos+1);
221
        } else {
222 2946
            $remainder = null;
223
        }
224
225 2970
        if ($pos == 0) {
226 1104
            if ($currentPart) {
227 1104
                $nextDigit = (int)$otherPart[count($otherPart)-1];
228
            } else {
229 1104
                $nextDigit = 0;
230
            }
231
        } else {
232 2844
            $nextDigit = (int)$roundedPart[$pos-1];
233
        }
234
235 2970
        return [$digit, $nextDigit, $remainder];
236
    }
237
238
    /**
239
     * @param array $roundedPart
240
     * @param array $otherPart
241
     * @param int $pos
242
     * @param int $carry
243
     * @param bool $currentPart
244
     * @return array
245
     */
246 2970
    private static function roundLoopEnd(
247
        array $roundedPart,
248
        array $otherPart,
249
        int $pos,
250
        int $carry,
251
        bool $currentPart
252
    ): array
253
    {
254 2970
        if ($pos == 0 && $carry == 1) {
255 983
            if ($currentPart) {
256 983
                $currentPart = false;
257
258
                // Do the variable swap dance
259 983
                $temp = $otherPart;
260 983
                $otherPart = $roundedPart;
261 983
                $roundedPart = $temp;
262
263 983
                $pos = count($roundedPart)-1;
264
            } else {
265 62
                array_unshift($roundedPart, $carry);
266 983
                $carry = 0;
267
            }
268
        } else {
269 2970
            $pos -= 1;
270
        }
271
272 2970
        return [$roundedPart, $otherPart, $pos, $carry, $currentPart];
273
    }
274
275
}