Completed
Push — master ( 71cd64...7b66c3 )
by Derek Stephen
01:19
created

Fraction::getStringWhole()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

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