1: <?php
2: 3: 4: 5: 6:
7:
8: namespace BN;
9:
10: 11: 12: 13: 14:
15: class Number implements INumber
16: {
17:
18: private $number;
19:
20: private $scale;
21:
22: 23: 24: 25: 26:
27: public function __construct($number = '0')
28: {
29: $this->checkConstructorArgument($number);
30: $this->number = $number;
31: $this->scale = null;
32: }
33:
34: public function add(INumber $number)
35: {
36: return $this->binaryOperator('bcadd', $number);
37: }
38:
39: public function subtract(INumber $number)
40: {
41: return $this->binaryOperator('bcsub', $number);
42: }
43:
44: public function multiply(INumber $number)
45: {
46: return $this->binaryOperator('bcmul', $number);
47: }
48:
49: public function divide(INumber $number)
50: {
51: $this->checkNumberIsNotZero($number);
52:
53: return $this->binaryOperator('bcdiv', $number);
54: }
55:
56: public function quotient(INumber $number)
57: {
58: return $this->divide($number)->roundDown();
59: }
60:
61: public function modulo(INumber $number)
62: {
63: $this->checkNumberIsNotZero($number);
64: $modulo = $this->getRealNumberModulo($number);
65: if ($this->isNegative()) {
66: return $modulo->negate();
67: }
68: return $modulo;
69: }
70:
71: private function getRealNumberModulo($number)
72: {
73: $quotient = $this->quotient($number)->abs();
74:
75: return $this->abs()->subtract($number->abs()->multiply($quotient));
76: }
77:
78: public function power(INumber $number)
79: {
80: if ($number->isInteger()) {
81: return $this->binaryOperator('bcpow', $number->round(0));
82: } elseif ($number->isEqual(new Number('0.5'))) {
83: return $this->sqrt();
84: } else {
85: return $this->powerOfDecimal($number);
86: }
87: }
88:
89: private function powerOfDecimal(INumber $number)
90: {
91: $floatResult = pow((float) $this->__toString(), (float) $number->__toString());
92: return new Number((string) $floatResult);
93: }
94:
95: public function sqrt()
96: {
97: if ($this->isNegative()) {
98: throw new \InvalidArgumentException();
99: }
100: return $this->unaryOperator('bcsqrt');
101: }
102:
103: public function abs()
104: {
105: if ($this->isNegative()) {
106: return $this->negate();
107: }
108: return clone $this;
109: }
110:
111: public function negate()
112: {
113: return $this->multiply(new Number('-1'));
114: }
115:
116: public function isInteger()
117: {
118: return !$this->hasDecimalPoint()
119: || preg_match("~\.[0]+$~", $this)
120: || substr($this, -1) == '.';
121: }
122:
123: private function hasDecimalPoint()
124: {
125: return strpos($this, '.') !== false;
126: }
127:
128: public function isZero()
129: {
130: return $this->isEqual(new Number());
131: }
132:
133: public function isNegative()
134: {
135: return $this->isSmallerThan(new Number());
136: }
137:
138: public function isPositive()
139: {
140: return $this->isBiggerThan(new Number());
141: }
142:
143: public function compare(INumber $number)
144: {
145: return bccomp((string) $this, (string) $number);
146: }
147:
148: public function isEqual(INumber $number)
149: {
150: return $this->compare($number) == 0;
151: }
152:
153: public function isBiggerThan(INumber $number)
154: {
155: return $this->compare($number) == 1;
156: }
157:
158: public function isSmallerThan(INumber $number)
159: {
160: return $this->compare($number) == -1;
161: }
162:
163: public function round($precision)
164: {
165: $precision = $this->transformPrecision($precision);
166: $this->checkPrecision($precision);
167: if ($precision >= 0) {
168: return $this->roundNumberAfterDecimalPoint($precision);
169: } else {
170: return $this->roundNumberBeforeDecimalPoint($precision);
171: }
172: }
173:
174: private function transformPrecision($precision)
175: {
176: if ($precision instanceof INumber) {
177: return intval($precision->round(0)->__toString());
178: }
179: return $precision;
180: }
181:
182: private function roundNumberAfterDecimalPoint($precision)
183: {
184: if (!$this->isInteger()) {
185: $operation = $this->isPositive() ? 'bcadd' : 'bcsub';
186: $number = $operation((string) $this, '0.' . str_repeat('0', $precision) . '5', $precision);
187: } elseif ($this->hasDecimalPoint()) {
188: $number = substr($this, 0, strpos($this, '.'));
189: } else {
190: $number = $this->__toString();
191: }
192: return new Number($number);
193: }
194:
195: private function roundNumberBeforeDecimalPoint($precision)
196: {
197: $powerOfTen = - pow(10, abs($precision));
198: return $this->roundToNumber((int) $powerOfTen);
199: }
200:
201: public function roundUp()
202: {
203: if ($this->isInteger()) {
204: return $this->round(0);
205: }
206: return $this->roundDown()->getIntegerFartherOneFromZero();
207: }
208:
209: private function getIntegerFartherOneFromZero()
210: {
211: $addedNumber = $this->isNegative() ? '-1' : '1';
212: $one = new Number($addedNumber);
213: return $this->add($one)->round(0);
214: }
215:
216: public function roundDown()
217: {
218: if ($this->isInteger()) {
219: return $this->round(0);
220: } elseif ($this->isNegative()) {
221: return $this->getNegativeNumberClosestToZero();
222: } else {
223: return $this->getPositiveNumberClosestToZero();
224: }
225: }
226:
227: private function getNegativeNumberClosestToZero()
228: {
229: return $this->getPositiveNumberClosestToZero()->negate();
230: }
231:
232: private function getPositiveNumberClosestToZero()
233: {
234: $number = bcadd((string) $this->abs(), 0, 0);
235: return new Number($number);
236: }
237:
238: public function roundToNumber($precision)
239: {
240: $precision = $this->transformPrecision($precision);
241: $this->checkPrecision($precision);
242: $absolutePrecision = new Number((string) abs($precision));
243: if ($precision < 0) {
244: return $this->getRoundByNearestMultiple($absolutePrecision);
245: } elseif ($precision > 0 && !$this->isInteger()) {
246: return $this->roundDecimalToNumber($absolutePrecision);
247: } else {
248: return $this->round(0);
249: }
250: }
251:
252: private function roundDecimalToNumber($absolutePrecision)
253: {
254: $precision = strlen($absolutePrecision);
255: $fractions = $this->round($precision)->getFractionalPartAsNewNumber($precision);
256: $modulo = $fractions->getRoundByNearestMultiple($absolutePrecision)->round(0);
257: return new Number("{$this->roundDown()}.{$modulo}");
258: }
259:
260: private function getFractionalPartAsNewNumber($precision)
261: {
262: $decimalNumber = $this->subtract($this->roundDown())->abs();
263: $fractionalPart = substr($decimalNumber, 2, $precision);
264: return new Number($fractionalPart);
265: }
266:
267: private function getRoundByNearestMultiple($absoluteMultiple)
268: {
269: $moduloDown = $this->modulo($absoluteMultiple)->abs();
270: $moduloUp = $absoluteMultiple->subtract($moduloDown);
271: if ($moduloDown->compare($moduloUp) >= 0) {
272: $operation = $this->isPositive() ? 'add' : 'subtract';
273: return $this->$operation($moduloUp);
274: } else {
275: $operation = $this->isPositive() ? 'subtract' : 'add';
276: return $this->$operation($moduloDown);
277: }
278: }
279:
280: public function setLocalScale($scale)
281: {
282: $this->checkScale($scale);
283: $this->scale = $scale;
284: }
285:
286: public function resetLocalScale()
287: {
288: $this->scale = null;
289: }
290:
291: public function __toString()
292: {
293: return $this->number;
294: }
295:
296: public function __clone()
297: {
298: return new Number($this->number);
299: }
300:
301: private function binaryOperator($callback, $number)
302: {
303: return $this->callBCMath($callback, (string) $this, (string) $number);
304: }
305:
306: private function unaryOperator($callback)
307: {
308: return $this->callBCMath($callback, (string) $this);
309: }
310:
311: 312: 313: 314: 315: 316:
317: private function callBCMath()
318: {
319: $parameters = func_get_args();
320: $callback = array_shift($parameters);
321: if ($this->isDefinedLocalScale()) {
322: $parameters[] = $this->scale;
323: }
324: $resultString = call_user_func_array($callback, $parameters);
325: return new Number($resultString);
326: }
327:
328: private function isDefinedLocalScale()
329: {
330: return $this->scale !== null;
331: }
332:
333: private function checkNumberIsNotZero($number)
334: {
335: if ($number->isEqual(new Number())) {
336: throw new \InvalidArgumentException();
337: }
338: }
339:
340: private function checkConstructorArgument($value)
341: {
342: if (!is_string($value) || !is_numeric($value)) {
343: throw new \InvalidArgumentException();
344: }
345: }
346:
347: private function checkPrecision($precision)
348: {
349: if (!is_int($precision)) {
350: throw new \InvalidArgumentException();
351: }
352: }
353:
354: private function checkScale($scale)
355: {
356: if (!is_int($scale) || $scale < 0) {
357: throw new \InvalidArgumentException();
358: }
359: }
360: }
361: