Completed
Push — master ( ed8239...71cd64 )
by Derek Stephen
09:54
created

Fraction::shouldRefactorWhole()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 5
ccs 1
cts 1
cp 1
rs 9.4285
cc 2
eloc 3
nc 2
nop 0
crap 2
1
<?php
2
3
namespace Del\Phi;
4
5
use Del\Phi\Exception\PhiException;
6
use InvalidArgumentException;
7
8
class Fraction
9
{
10
    /** @var int $whole */
11
    private $whole;
12
13
    /** @var int $numerator */
14
    private $numerator;
15
16
    /** @var int $denominator */
17
    private $denominator;
18
19 5
    /** @var bool $negative */
20
    private $negative;
21 5
22 5
    public function __construct($whole = 0, $numerator = 0, $denominator = 1)
23 5
    {
24 5
        $this->whole = $whole;
25 5
        $this->numerator = $numerator;
26
        $this->denominator = $denominator;
27
        $this->negative = false;
28
    }
29
30 1
    /**
31
     * @return int
32 1
     */
33
    public function getWhole()
34
    {
35
        return $this->whole;
36
    }
37
38
    /**
39 4
     * @param int $whole
40
     * @return Fraction
41 4
     */
42 4
    public function setWhole($whole)
43
    {
44
        $this->throwExceptionIfNegative($whole);
45
        $this->whole = $whole;
46
        return $this;
47
    }
48 1
49
    /**
50 1
     * @return int
51
     */
52
    public function getNumerator()
53
    {
54
        return $this->numerator;
55
    }
56
57 4
    /**
58
     * @param int $numerator
59 4
     * @return Fraction
60 4
     */
61
    public function setNumerator($numerator)
62
    {
63
        $this->throwExceptionIfNegative($numerator);
64
        $this->numerator = $numerator;
65
        return $this;
66 1
    }
67
68 1
    /**
69
     * @return int
70
     */
71
    public function getDenominator()
72
    {
73
        return $this->denominator;
74
    }
75 4
76
    /**
77 4
     * @param int $denominator
78 4
     * @return Fraction
79
     */
80
    public function setDenominator($denominator)
81 1
    {
82
        $this->throwExceptionIfNegative($denominator);
83
        $this->denominator = $denominator;
84 1
        return $this;
85 1
    }
86 1
87 1
    private function refactor()
88 1
    {
89 1
        // 9/8 would become 1 1/8 for instance
90 1
        if ($this->shouldRefactorWhole()) {
91
            $this->refactorWhole();
92
        }
93
        if ($this->shouldRefactorFraction()) {
94
            $this->refactorFraction();
95 1
        }
96
    }
97 1
98 1
    /**
99
     * @return bool
100
     */
101
    private function shouldRefactorWhole()
102
    {
103
        return $this->numerator >= $this->denominator
104 1
            && $this->denominator > 0;
105
    }
106 1
107 1
    /**
108
     * @return bool
109
     */
110 1
    private function shouldRefactorFraction()
111
    {
112
        return $this->numerator > 0
113 1
            && $this->denominator > 0;
114 1
    }
115 1
116 1
    private function refactorWhole()
117 1
    {
118
        // decrement $x and the numerator by the denominator each loop, and add to the whole
119 1
        for ($x = $this->numerator; $x >= $this->denominator; $x = $x - $this->denominator) {
120
            $this->whole ++;
121 1
            $this->numerator -= $this->denominator;
122 1
        }
123 1
    }
124 1
125
    private function refactorFraction()
126
    {
127
        $gcd = $this->getGreatestCommonDenominator($this->numerator, $this->denominator);
128
        $this->numerator = $this->numerator / $gcd;
129
        $this->denominator = $this->denominator / $gcd;
130
    }
131 1
132
    /**
133
     * @param int $x
134 1
     * @param int $y
135 1
     * @return int
136
     */
137
    private function getGreatestCommonDenominator($x, $y)
138 1
    {
139
        // first get the common denominators of both numerator and denominator
140
        $factorsX = $this->getFactors($x);
141 1
        $factorsY = $this->getFactors($y);
142
143 1
        // common denominators will be in both arrays, so get the intersect
144
        $commonDenominators = array_intersect($factorsX, $factorsY);
145
146
        // greatest common denominator is the highest number (last in the array)
147
        $gcd = array_pop($commonDenominators);
148
149
        return $gcd;
150 1
    }
151
152 1
    /**
153
     * @param int $num
154 1
     * @return array The common denominators of $num
155 1
     */
156 1
    private function getFactors($num)
157 1
    {
158 1
        $factors = [];
159 1
        // get factors of the numerator
160
        for ($x = 1; $x <= $num; $x ++) {
161
            if ($num % $x == 0) {
162
                $factors[] = $x;
163
            }
164
        }
165 1
        return $factors;
166
    }
167 1
168
    /**
169
     * @return bool
170
     */
171
    public function isNegative()
172
    {
173
        return $this->negative;
174 1
    }
175
176 1
    /**
177 1
     * @param bool $negative
178
     * @return Fraction
179
     */
180
    public function setNegative($negative)
181
    {
182
        $this->negative = $negative;
183 1
        return $this;
184
    }
185 1
186
    /**
187
     * @return bool
188
     */
189
    public function isInteger()
190
    {
191 1
        return $this->numerator % $this->denominator == 0;
192
    }
193 1
194
    /**
195
     * @return string
196 1
     */
197 1
    public function __toString()
198 1
    {
199
        $this->refactor();
200
201
        // if the whole is 0, don't display it
202
        $whole = $this->getStringWhole();
203
        $fraction = $this->getStringFraction();
204 1
        return $this->formatString($whole, $fraction);
205
    }
206 1
207
    /**
208
     * @return string
209
     */
210
    private function getStringWhole()
211
    {
212 1
        return $this->whole == 0 ? '' : (string) $this->whole;
213
    }
214 1
215
    /**
216
     * @return string
217
     */
218
    private function getStringFraction()
219
    {
220
        return $this->numerator > 0 && $this->denominator > 0 ? $this->numerator.'/'.$this->denominator : '';;
221
    }
222 1
223
    /**
224 1
     * @param string $whole
225 1
     * @param string $fraction
226
     * @return string
227
     */
228
    private function formatString($whole, $fraction)
229
    {
230
        $space = ($whole && $fraction) ? ' ' : '';
231 1
        $negative = $this->isNegative() ? '-' : '';
232
        return empty($negative.$whole.$space.$fraction) ? '0' : $negative.$whole.$space.$fraction;
233
    }
234
235
    /**
236 1
     * @return float
237 1
     */
238 1
    public function toDecimal()
239
    {
240
        /*
241
         * a divide symbol. so this is broken and will need refactoring to be accurate. ;-)
242
         */
243
        $decimal = $this->numerator / $this->denominator;
244
        $number =  $this->whole + $decimal;
245
        $number = $this->isNegative() ? $number - $number - $number : $number;
246
        return $number;
247
    }
248
249
    /**
250
     * @param $num
251
     * @throws PhiException
252
     */
253
    public function throwExceptionIfNegative($num)
254
    {
255
        if ($num < 0) {
256
            throw new PhiException(PhiException::ERROR_NEGATIVE_NUMBER);
257
        }
258
    }
259
}