Toolkit::roundAwayFromZero()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 2
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
ccs 6
cts 6
cp 1
crap 3
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace byrokrat\amount\Rounder;
6
7
/**
8
 * Rounding helper methods
9
 */
10
class Toolkit
11
{
12
    /**
13
     * Check if value is positive
14
     */
15 236
    public function isPositive(string $value): bool
16
    {
17 236
        return 1 == bccomp($value, '0', $this->parsePrecision($value));
18
    }
19
20
    /**
21
     * Check if value is even
22
     */
23 13
    public function isEven(string $value): bool
24
    {
25 13
        return 0 == bcmod($value, '2');
26
    }
27
28
    /**
29
     * Extract the precision used in $value
30
     */
31 292
    public function parsePrecision(string $value): int
32
    {
33 292
        if (strpos($value, '.') === false) {
34 19
            return 0;
35
        }
36
37 277
        list(, $fraction) = explode('.', $value);
38
39 277
        return strlen(rtrim($fraction, '0'));
40
    }
41
42
    /**
43
     * Get one unit in the scale of $precision
44
     */
45 55
    public function getOneUnit(int $precision): string
46
    {
47 55
        return bcdiv(
48 55
            '1',
49 55
            '1' . str_repeat('0', $precision),
50 55
            $precision
51
        );
52
    }
53
54
    /**
55
     * Round value towards zero
56
     */
57 261
    public function roundTowardsZero(string $value, int $precision): string
58
    {
59 261
        return bcadd($value, '0', $precision);
60
    }
61
62
    /**
63
     * Round value away from zero
64
     */
65 22
    public function roundAwayFromZero(string $value, int $precision): string
66
    {
67 22
        if ($this->parsePrecision($value) <= $precision) {
68 2
            return $value;
69
        }
70
71 20
        if ($this->isPositive($value)) {
72 10
            return $this->roundUp($value, $precision);
73
        }
74
75 10
        return $this->roundDown($value, $precision);
76
    }
77
78
    /**
79
     * Round up value
80
     */
81 44 View Code Duplication
    public function roundUp(string $value, int $precision): string
82
    {
83 44
        if ($this->parsePrecision($value) <= $precision) {
84 2
            return $value;
85
        }
86
87 42
        if ($this->isPositive($value)) {
88 27
            return bcadd(
89 27
                $this->roundTowardsZero($value, $precision),
90 27
                $this->getOneUnit($precision),
91 27
                $precision
92
            );
93
        }
94
95 15
        return $this->roundTowardsZero($value, $precision);
96
    }
97
98
    /**
99
     * Round down value
100
     */
101 198 View Code Duplication
    public function roundDown(string $value, int $precision): string
102
    {
103 198
        if ($this->parsePrecision($value) <= $precision) {
104 3
            return $value;
105
        }
106
107 195
        if ($this->isPositive($value)) {
108 172
            return $this->roundTowardsZero($value, $precision);
109
        }
110
111 23
        return bcsub(
112 23
            $this->roundTowardsZero($value, $precision),
113 23
            $this->getOneUnit($precision),
114 23
            $precision
115
        );
116
    }
117
118
    /**
119
     * Get tiebreak value for round to nearest strategies
120
     */
121 184
    public function getTiebreak(string $value, int $precision): string
122
    {
123 184
        return $this->roundTowardsZero($value, $precision) . ($precision > 0 ? '5' : '.5');
124
    }
125
126
    /**
127
     * Round to nearest using callback for breaking ties
128
     */
129 221
    public function roundToNearest(string $value, int $precision, callable $tiebreakCallback): string
130
    {
131 221
        if ($this->parsePrecision($value) <= $precision) {
132 42
            return $value;
133
        }
134
135 180
        switch (bccomp($value, $this->getTiebreak($value, $precision), $precision + 1)) {
136 180
            case 1:
137 2
                return $this->roundUp($value, $precision);
138 178
            case -1:
139 158
                return $this->roundDown($value, $precision);
140
            default:
141 20
                return $tiebreakCallback($value, $precision);
142
        }
143
    }
144
}
145