Overview

Namespaces

  • BN
    • Collections
    • Compiler
      • Grammar
      • Parser
        • Operator
        • Token
      • Postfix
        • Operands
        • Operator
        • Token
      • Scanner
        • Converter
      • Token
  • Demo
  • None

Classes

  • AggregateFunctions
  • Number
  • NumberFactory
  • OperatorsFactory

Interfaces

  • INumber
  • Overview
  • Namespace
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * BN-PHP (https://bitbucket.org/zdenekdrahos/bn-php)
  4:  * @license New BSD License
  5:  * @author Zdenek Drahos
  6:  */
  7: 
  8: namespace BN;
  9: 
 10: /**
 11:  * Big Number data type for PHP. Uses BC Math for arbitrary precision mathematics
 12:  * with numbers of any size and precision. Object is immutable and the inner
 13:  * representation of number is a string.
 14:  */
 15: class Number implements INumber
 16: {
 17:     /** @var string */
 18:     private $number;
 19:     /** @var int */
 20:     private $scale;
 21: 
 22:     /**
 23:      * Sets a number and reset local scale (instance will use only global scale).
 24:      * @param  string                    $number
 25:      * @throws \InvalidArgumentException if $number is not numeric string
 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)); // round because integer can be 2.00
 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(); // e.g. 0.851
263:         $fractionalPart = substr($decimalNumber, 2, $precision); // e.g. 851
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); // same as implicit clone in PHP (it's a unnecessary)
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:      * Parameters:
313:      * - first parameter is name of function from BC Math
314:      * - next parameters are parameters for function
315:      * It adds scale as last parameter if local scale is defined
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: 
BN-PHP - Big Number data type for PHP API documentation generated by ApiGen 2.8.0