<?php
/**
* This file is part of the moontoast/math library
*
* Copyright 2013-2020 Moontoast, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Moontoast\Math;
/**
* Represents a number for use with Binary Calculator computations
*
* @link http://www.php.net/bcmath
*/
class BigNumber extends AbstractBigNumber
{
/**
* Number value, as a string
*
* @var string
*/
protected $numberValue;
/**
* The scale for the current number
*
* @var int
*/
protected $numberScale;
/**
* Constructs a BigNumber object from a string, integer, float, or any
* object that may be cast to a string, resulting in a numeric string value
*
* @param mixed $number May be of any type that can be cast to a string
* representation of a base 10 number
* @param int $scale (optional) Specifies the default number of digits after the decimal
* place to be used in operations for this BigNumber
* @return void
*/
public function __construct($number, $scale = null)
{
if ($scale) {
$this->setScale($scale);
}
$this->setValue($number);
}
/**
* Returns the string value of this BigNumber
*
* @return string String representation of the number in base 10
*/
public function __toString()
{
return (string) $this->getValue();
}
/**
* Sets the current number to the absolute value of itself
*
* @return BigNumber for fluent interface
*/
public function abs()
{
$this->numberValue = \ltrim($this->numberValue, '-');
return $this;
}
/**
* Adds the given number to the current number
*
* @param mixed $number May be of any type that can be cast to a string
* representation of a base 10 number
* @return BigNumber for fluent interface
* @link http://www.php.net/bcadd
*/
public function add($number)
{
$this->numberValue = \bcadd(
$this->numberValue,
$this->filterNumber($number),
$this->getScale()
);
return $this;
}
/**
* Finds the next highest integer value by rounding up the current number
* if necessary
*
* @return BigNumber for fluent interface
* @link http://www.php.net/ceil
*/
public function ceil()
{
$number = $this->getValue();
if ($this->isPositive()) {
// 14 is the magic precision number
$number = \bcadd($number, '0', 14);
if (\substr($number, -15) != '.00000000000000') {
$number = \bcadd($number, '1', 0);
}
}
$this->numberValue = \bcadd($number, '0', 0);
return $this;
}
/**
* Compares the current number with the given number
*
* Returns 0 if the two operands are equal, 1 if the current number is
* larger than the given number, -1 otherwise.
*
* @param mixed $number May be of any type that can be cast to a string
* representation of a base 10 number
* @return int
* @link http://www.php.net/bccomp
*/
public function compareTo($number)
{
$number = $this->filterNumber($number);
// http://bugs.php.net/46781 changed how bcmath compares minus zero. for BC purposes we emulate the old
// behavior, prior to the bug fix
if (preg_match('#^-?0\.0+?$#', $this->numberValue) || preg_match('#^-?0\.0+?$#', $number)) {
if ($this->numberValue[0] === $number[0]) {
return 0;
}
return $this->numberValue[0] === '-' ? -1 : 1;
}
return bccomp(
$this->numberValue,
$number,
$this->getScale()
);
}
/**
* Returns the current value converted to an arbitrary base
*
* @param int $base The base to convert the current number to
* @return string String representation of the number in the given base
*/
public function convertToBase($base)
{
return self::convertFromBase10($this->getValue(), $base);
}
/**
* Decreases the value of the current number by one
*
* @return BigNumber for fluent interface
*/
public function decrement()
{
return $this->subtract(1);
}
/**
* Divides the current number by the given number
*
* @param mixed $number May be of any type that can be cast to a string
* representation of a base 10 number
* @return BigNumber for fluent interface
* @throws Exception\ArithmeticException if $number is zero
* @link http://www.php.net/bcdiv
*/
public function divide($number)
{
$number = $this->filterNumber($number);
if ($number == '0') {
throw new Exception\ArithmeticException('Division by zero');
}
$this->numberValue = \bcdiv(
$this->numberValue,
$number,
$this->getScale()
);
return $this;
}
/**
* Finds the next lowest integer value by rounding down the current number
* if necessary
*
* @return BigNumber for fluent interface
* @link http://www.php.net/floor
*/
public function floor()
{
$number = $this->getValue();
if ($this->isNegative()) {
// 14 is the magic precision number
$number = \bcadd($number, '0', 14);
if (\substr($number, -15) != '.00000000000000') {
$number = \bcsub($number, '1', 0);
}
}
$this->numberValue = \bcadd($number, '0', 0);
return $this;
}
/**
* Returns the scale used for this BigNumber
*
* If no scale was set, this will default to the value of bcmath.scale
* in php.ini.
*
* @return int
*/
public function getScale()
{
if ($this->numberScale === null) {
if (version_compare(PHP_VERSION, '7.3.0') >= 0 || !extension_loaded('bcmath')) {
// @codeCoverageIgnoreStart
return (string) bcscale();
// @codeCoverageIgnoreEnd
}
return (string) max(0, strlen(bcadd('0', '0')) - 2);
}
return $this->numberScale;
}
/**
* Returns the current raw value of this BigNumber
*
* @return string String representation of the number in base 10
*/
public function getValue()
{
return $this->numberValue;
}
/**
* Increases the value of the current number by one
*
* @return BigNumber for fluent interface
*/
public function increment()
{
return $this->add(1);
}
/**
* Finds the modulus of the current number divided by the given number
*
* @param mixed $number May be of any type that can be cast to a string
* representation of a base 10 number
* @return BigNumber for fluent interface
* @throws Exception\ArithmeticException if $number is zero
* @link http://www.php.net/bcmod
*/
public function mod($number)
{
$number = $this->filterNumber($number);
if ($number == '0') {
throw new Exception\ArithmeticException('Division by zero');
}
$this->numberValue = \bcmod(
$this->numberValue,
$number
);
return $this;
}
/**
* Multiplies the current number by the given number
*
* @param mixed $number May be of any type that can be cast to a string
* representation of a base 10 number
* @return BigNumber for fluent interface
* @link http://www.php.net/bcmul
*/
public function multiply($number)
{
$this->numberValue = \bcmul(
$this->numberValue,
$this->filterNumber($number),
$this->getScale()
);
return $this;
}
/**
* Sets the current number to the negative value of itself
*
* @return BigNumber for fluent interface
*/
public function negate()
{
return $this->multiply(-1);
}
/**
* Raises current number to the given number
*
* @param mixed $number May be of any type that can be cast to a string
* representation of a base 10 number
* @return BigNumber for fluent interface
* @link http://www.php.net/bcpow
*/
public function pow($number)
{
$this->numberValue = \bcpow(
$this->numberValue,
$this->filterNumber($number),
$this->getScale()
);
return $this;
}
/**
* Raises the current number to the $pow, then divides by the $mod
* to find the modulus
*
* This is functionally equivalent to the following code:
*
* <code>
* $n = new BigNumber(1234);
* $n->mod($n->pow(32), 2);
* </code>
*
* However, it uses bcpowmod(), so it is faster and can accept larger
* parameters.
*
* @param mixed $pow May be of any type that can be cast to a string
* representation of a base 10 number
* @param mixed $mod May be of any type that can be cast to a string
* representation of a base 10 number
* @return BigNumber for fluent interface
* @throws Exception\ArithmeticException if $number is zero
* @link http://www.php.net/bcpowmod
*/
public function powMod($pow, $mod)
{
$mod = $this->filterNumber($mod);
if ($mod == '0') {
throw new Exception\ArithmeticException('Division by zero');
}
$this->numberValue = \bcpowmod(
$this->numberValue,
$this->filterNumber($pow),
$mod,
$this->getScale()
);
return $this;
}
/**
* Rounds the current number to the nearest integer
*
* @return BigNumber for fluent interface
* @todo Implement precision digits
*/
public function round()
{
$original = $this->getValue();
$floored = $this->floor()->getValue();
$diff = \bcsub($original, $floored, 20);
if ($this->isNegative()) {
$roundedDiff = \round($diff, 0, PHP_ROUND_HALF_DOWN);
} else {
$roundedDiff = \round($diff);
}
$this->numberValue = \bcadd(
$floored,
$roundedDiff,
0
);
return $this;
}
/**
* Sets the scale of this BigNumber
*
* @param int $scale Specifies the default number of digits after the decimal
* place to be used in operations for this BigNumber
* @return BigNumber for fluent interface
*/
public function setScale($scale)
{
$this->numberScale = (int) $scale;
return $this;
}
/**
* Sets the value of this BigNumber to a new value
*
* @param mixed $number May be of any type that can be cast to a string
* representation of a base 10 number
* @return BigNumber for fluent interface
*/
public function setValue($number)
{
// Set the scale for the number to the scale value passed in
$number = \bcadd(
$this->filterNumber($number),
'0',
$this->getScale()
);
$this->numberValue = $number;
return $this;
}
/**
* Shifts the current number $bits to the left
*
* @param int $bits
* @return BigNumber for fluent interface
*/
public function shiftLeft($bits)
{
$this->numberValue = \bcmul(
$this->numberValue,
\bcpow('2', $bits)
);
return $this;
}
/**
* Shifts the current number $bits to the right
*
* @param int $bits
* @return BigNumber for fluent interface
*/
public function shiftRight($bits)
{
$this->numberValue = \bcdiv(
$this->numberValue,
\bcpow('2', $bits)
);
return $this;
}
/**
* Finds the square root of the current number
*
* @return BigNumber for fluent interface
* @link http://www.php.net/bcsqrt
*/
public function sqrt()
{
$this->numberValue = \bcsqrt(
$this->numberValue,
$this->getScale()
);
return $this;
}
/**
* Subtracts the given number from the current number
*
* @param mixed $number May be of any type that can be cast to a string
* representation of a base 10 number
* @return BigNumber for fluent interface
* @link http://www.php.net/bcsub
*/
public function subtract($number)
{
$this->numberValue = \bcsub(
$this->numberValue,
$this->filterNumber($number),
$this->getScale()
);
return $this;
}
/**
* Filters a number, converting it to a string value
*
* @param mixed $number
* @return string
*/
protected function filterNumber($number)
{
return \filter_var(
$number,
FILTER_SANITIZE_NUMBER_FLOAT,
FILTER_FLAG_ALLOW_FRACTION
);
}
}