Version 5.0.0 beta 1
This commit is contained in:
1 parent
25ddeb65d6
commit
15c7beabc5
6736 files changed
+627902
-497943
No files matched your search
+84
-74
@@ -11,62 +11,72 @@
|
||||
namespace IPS\Math;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use InvalidArgumentException;
|
||||
use JsonSerializable;
|
||||
use RuntimeException;
|
||||
use function defined;
|
||||
use function extension_loaded;
|
||||
use function intval;
|
||||
use function is_string;
|
||||
use function substr;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number Class for precise math
|
||||
*/
|
||||
class _Number implements \JsonSerializable
|
||||
class Number implements JsonSerializable
|
||||
{
|
||||
/* !Bootstrap */
|
||||
|
||||
/**
|
||||
* @brief Positive
|
||||
*/
|
||||
protected $positive = TRUE;
|
||||
protected bool $positive = TRUE;
|
||||
|
||||
/**
|
||||
* @brief Number before decimal point
|
||||
*/
|
||||
protected $beforeDecimalPoint = 0;
|
||||
protected int $beforeDecimalPoint = 0;
|
||||
|
||||
/**
|
||||
* @brief Number of decimal places
|
||||
*/
|
||||
protected $numberOfDecimalPlaces = 0;
|
||||
protected int $numberOfDecimalPlaces = 0;
|
||||
|
||||
/**
|
||||
* @brief After decimal point
|
||||
*/
|
||||
protected $afterDecimalPoint = 0;
|
||||
protected int $afterDecimalPoint = 0;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $number The number, as a string, using "." for decimal points
|
||||
* @param string $number The number, as a string, using "." for decimal points
|
||||
* @return void
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct( $number )
|
||||
public function __construct( string $number )
|
||||
{
|
||||
/* Check it's valid */
|
||||
if ( !\is_string( $number ) )
|
||||
if ( !is_string( $number ) )
|
||||
{
|
||||
throw new \InvalidArgumentException('NOT_A_STRING');
|
||||
throw new InvalidArgumentException('NOT_A_STRING');
|
||||
}
|
||||
|
||||
if ( $number === '' )
|
||||
{
|
||||
throw new \InvalidArgumentException('NOT_VALID_NUMBER');
|
||||
throw new InvalidArgumentException('NOT_VALID_NUMBER');
|
||||
}
|
||||
|
||||
if ( !preg_match( '/^([+-])?(\d*)(\.(\d*))?$/', $number, $matches ) )
|
||||
{
|
||||
throw new \InvalidArgumentException('NOT_VALID_NUMBER');
|
||||
throw new InvalidArgumentException('NOT_VALID_NUMBER');
|
||||
}
|
||||
|
||||
/* Set properties */
|
||||
@@ -74,9 +84,9 @@ class _Number implements \JsonSerializable
|
||||
{
|
||||
$this->positive = FALSE;
|
||||
}
|
||||
$this->beforeDecimalPoint = isset( $matches[2] ) ? \intval( $matches[2] ) : 0;
|
||||
$this->beforeDecimalPoint = isset( $matches[2] ) ? intval( $matches[2] ) : 0;
|
||||
$this->numberOfDecimalPlaces = isset( $matches[4] ) ? mb_strlen( $matches[4] ) : 0;
|
||||
$this->afterDecimalPoint = isset( $matches[4] ) ? \intval( $matches[4] ) : 0;
|
||||
$this->afterDecimalPoint = isset( $matches[4] ) ? intval( $matches[4] ) : 0;
|
||||
|
||||
/* If we have x.y00, simplify to x.y */
|
||||
$this->_simplifyDecimalPlaces();
|
||||
@@ -87,7 +97,7 @@ class _Number implements \JsonSerializable
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isPositive()
|
||||
public function isPositive(): bool
|
||||
{
|
||||
return $this->positive;
|
||||
}
|
||||
@@ -97,7 +107,7 @@ class _Number implements \JsonSerializable
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isZero()
|
||||
public function isZero(): bool
|
||||
{
|
||||
return ( !$this->beforeDecimalPoint and !$this->afterDecimalPoint );
|
||||
}
|
||||
@@ -107,20 +117,20 @@ class _Number implements \JsonSerializable
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isGreaterThanZero()
|
||||
public function isGreaterThanZero(): bool
|
||||
{
|
||||
return $this->isPositive() and ( $this->beforeDecimalPoint or $this->afterDecimalPoint );
|
||||
}
|
||||
|
||||
/* !Basic Math */
|
||||
|
||||
|
||||
/**
|
||||
* Add
|
||||
*
|
||||
* @param \IPS\Math\Number $number Number to add
|
||||
* @return \IPS\Math\Number
|
||||
* @param Number $number Number to add
|
||||
* @return Number
|
||||
*/
|
||||
public function add( Number $number )
|
||||
public function add( Number $number ): Number
|
||||
{
|
||||
/* This method handles adding two positive numbers, if either (or both) are negative, pass off to subtract() as appropriate */
|
||||
if ( !$this->positive )
|
||||
@@ -152,8 +162,8 @@ class _Number implements \JsonSerializable
|
||||
}
|
||||
|
||||
/* Otherwise, add the two absolute values together */
|
||||
$paddedThis = \intval( "{$this->beforeDecimalPoint}" . str_pad( str_pad( $this->afterDecimalPoint, $this->numberOfDecimalPlaces, '0', STR_PAD_LEFT ), $biggerDecimalPlaces, '0' ) );
|
||||
$paddedNumber = \intval( "{$number->beforeDecimalPoint}" . str_pad( str_pad( $number->afterDecimalPoint, $number->numberOfDecimalPlaces, '0', STR_PAD_LEFT ), $biggerDecimalPlaces, '0' ) );
|
||||
$paddedThis = intval( "{$this->beforeDecimalPoint}" . str_pad( str_pad( $this->afterDecimalPoint, $this->numberOfDecimalPlaces, '0', STR_PAD_LEFT ), $biggerDecimalPlaces, '0' ) );
|
||||
$paddedNumber = intval( "{$number->beforeDecimalPoint}" . str_pad( str_pad( $number->afterDecimalPoint, $number->numberOfDecimalPlaces, '0', STR_PAD_LEFT ), $biggerDecimalPlaces, '0' ) );
|
||||
$absoluteResult = $paddedThis + $paddedNumber;
|
||||
|
||||
/* ... and then add the decimal back in the correct place */
|
||||
@@ -167,10 +177,10 @@ class _Number implements \JsonSerializable
|
||||
/**
|
||||
* Subtract
|
||||
*
|
||||
* @param \IPS\Math\Number $number Number to subtract
|
||||
* @return \IPS\Math\Number
|
||||
* @param Number $number Number to subtract
|
||||
* @return Number
|
||||
*/
|
||||
public function subtract( $number )
|
||||
public function subtract( Number $number ): Number
|
||||
{
|
||||
/* This method handles subtracting two positive numbers, if either (or both) are negative, pass off to add() as appropriate */
|
||||
if ( !$this->positive )
|
||||
@@ -202,8 +212,8 @@ class _Number implements \JsonSerializable
|
||||
}
|
||||
|
||||
/* Otherwise, add the two absolute values together */
|
||||
$paddedThis = \intval( "{$this->beforeDecimalPoint}" . str_pad( str_pad( $this->afterDecimalPoint, $this->numberOfDecimalPlaces, '0', STR_PAD_LEFT ), $biggerDecimalPlaces, '0' ) );
|
||||
$paddedNumber = \intval( "{$number->beforeDecimalPoint}" . str_pad( str_pad( $number->afterDecimalPoint, $number->numberOfDecimalPlaces, '0', STR_PAD_LEFT ), $biggerDecimalPlaces, '0' ) );
|
||||
$paddedThis = intval( "{$this->beforeDecimalPoint}" . str_pad( str_pad( $this->afterDecimalPoint, $this->numberOfDecimalPlaces, '0', STR_PAD_LEFT ), $biggerDecimalPlaces, '0' ) );
|
||||
$paddedNumber = intval( "{$number->beforeDecimalPoint}" . str_pad( str_pad( $number->afterDecimalPoint, $number->numberOfDecimalPlaces, '0', STR_PAD_LEFT ), $biggerDecimalPlaces, '0' ) );
|
||||
$absoluteResult = $paddedThis - $paddedNumber;
|
||||
|
||||
/* ... and then add the decimal back in the correct place */
|
||||
@@ -225,13 +235,13 @@ class _Number implements \JsonSerializable
|
||||
/**
|
||||
* Multiple
|
||||
*
|
||||
* @param \IPS\Math\Number $number Number to multiply by
|
||||
* @return \IPS\Math\Number
|
||||
* @param Number $number Number to multiply by
|
||||
* @return Number
|
||||
*/
|
||||
public function multiply( $number )
|
||||
public function multiply( Number $number ): Number
|
||||
{
|
||||
/* Multiply the absolute numbers */
|
||||
$absoluteResult = \intval( "{$this->beforeDecimalPoint}" . ( $this->afterDecimalPoint ? str_pad( $this->afterDecimalPoint, $this->numberOfDecimalPlaces, '0', STR_PAD_LEFT ) : '' ) ) * \intval( "{$number->beforeDecimalPoint}" . ( $number->afterDecimalPoint ? str_pad( $number->afterDecimalPoint, $number->numberOfDecimalPlaces, '0', STR_PAD_LEFT ) : '' ) );
|
||||
$absoluteResult = intval( "{$this->beforeDecimalPoint}" . ( $this->afterDecimalPoint ? str_pad( $this->afterDecimalPoint, $this->numberOfDecimalPlaces, '0', STR_PAD_LEFT ) : '' ) ) * intval( "{$number->beforeDecimalPoint}" . ( $number->afterDecimalPoint ? str_pad( $number->afterDecimalPoint, $number->numberOfDecimalPlaces, '0', STR_PAD_LEFT ) : '' ) );
|
||||
|
||||
/* Work out where the decimal point goes */
|
||||
$numberOfDecimalPlaces = $this->numberOfDecimalPlaces + $number->numberOfDecimalPlaces;
|
||||
@@ -268,18 +278,18 @@ class _Number implements \JsonSerializable
|
||||
/**
|
||||
* Divide
|
||||
*
|
||||
* @param \IPS\Math\Number $number Number to divide by
|
||||
* @param int $precision The precision to work to
|
||||
* @param Number $number Number to divide by
|
||||
* @param int $precision The precision to work to
|
||||
* @param int|NULL $round Handles how to round the result. See ROUND_* constants
|
||||
* @return \IPS\Math\Number
|
||||
* @throws \RuntimeException
|
||||
* @return Number
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function divide( $number, $precision = 3, $round = \IPS\Math\Number::ROUND_TRUNCATE )
|
||||
public function divide( Number $number, int $precision = 3, ?int $round = Number::ROUND_TRUNCATE ): Number
|
||||
{
|
||||
/* Can't divide by 0 */
|
||||
if ( $number->compare( new static('0') ) === 0 )
|
||||
{
|
||||
throw new \RuntimeException('DIVIDE_BY_ZERO');
|
||||
throw new RuntimeException('DIVIDE_BY_ZERO');
|
||||
}
|
||||
|
||||
/* Divide by 1 does nothing */
|
||||
@@ -295,7 +305,7 @@ class _Number implements \JsonSerializable
|
||||
}
|
||||
|
||||
/* If the bcmath extension is available, we can use bcdiv to do this effectively */
|
||||
if ( \extension_loaded('bcmath') )
|
||||
if ( extension_loaded('bcmath') )
|
||||
{
|
||||
$result = str_replace( '.', '', bcdiv( (string) $this->absolute(), (string) $number->absolute(), $precision ) ); // It will automatically return with correct $precision - e.g. "1.000" rather than "1", so we can just strip the decimal to get the absolute value we need
|
||||
}
|
||||
@@ -309,7 +319,7 @@ class _Number implements \JsonSerializable
|
||||
/* Round it */
|
||||
if ( $round !== static::ROUND_TRUNCATE )
|
||||
{
|
||||
$lastDigit = \intval( mb_substr( $result, -1 ) );
|
||||
$lastDigit = intval( mb_substr( $result, -1 ) );
|
||||
$result = mb_substr( $result, 0, -1 );
|
||||
|
||||
if ( $lastDigit !== 0 )
|
||||
@@ -353,11 +363,11 @@ class _Number implements \JsonSerializable
|
||||
/**
|
||||
* Modulus
|
||||
*
|
||||
* @param \IPS\Math\Number $number Number to get modulus of
|
||||
* @param int $divides Dividend
|
||||
* @return \IPS\Math\Number
|
||||
* @param Number $number Number to get modulus of
|
||||
* @param int $divides Dividend
|
||||
* @return Number
|
||||
*/
|
||||
public function modulus( $number, &$divides = 0 )
|
||||
public function modulus( Number $number, int &$divides = 0 ): Number
|
||||
{
|
||||
/* Start with positiver numbers */
|
||||
$remainder = clone $this;
|
||||
@@ -396,10 +406,10 @@ class _Number implements \JsonSerializable
|
||||
/**
|
||||
* Compare
|
||||
*
|
||||
* @param \IPS\Math\Number $number Number to compare against
|
||||
* @param Number $number Number to compare against
|
||||
* @return int Returns 0 if the two numbers are equal, 1 if this number is larger than the $number, -1 otherwise
|
||||
*/
|
||||
public function compare( $number )
|
||||
public function compare( Number $number ): int
|
||||
{
|
||||
list( $number1, $number2 ) = static::_normaliseTwoNumbers( $this, $number );
|
||||
|
||||
@@ -440,11 +450,11 @@ class _Number implements \JsonSerializable
|
||||
/**
|
||||
* Round to a number of decimal places
|
||||
*
|
||||
* @param int $decimalPlaces The number of decimal places to round to
|
||||
* @param int $decimalPlaces The number of decimal places to round to
|
||||
* @param int $round Handles how to round. See ROUND_* constants
|
||||
* @return \IPS\Math\Number
|
||||
* @return Number
|
||||
*/
|
||||
public function round( $decimalPlaces, $round = \IPS\Math\Number::ROUND_NORMAL )
|
||||
public function round( int $decimalPlaces, int $round = Number::ROUND_NORMAL ): Number
|
||||
{
|
||||
/* If it's already precise enough, we don't need to do anything */
|
||||
if ( $decimalPlaces >= $this->numberOfDecimalPlaces )
|
||||
@@ -455,7 +465,7 @@ class _Number implements \JsonSerializable
|
||||
/* If we don't know if we're going up or down, figure that out */
|
||||
if ( $round === static::ROUND_NORMAL )
|
||||
{
|
||||
$numberToExamine = \intval( \substr( str_pad( $this->afterDecimalPoint, $this->numberOfDecimalPlaces, '0', STR_PAD_LEFT ), $decimalPlaces, 1 ) );
|
||||
$numberToExamine = intval( substr( str_pad( $this->afterDecimalPoint, $this->numberOfDecimalPlaces, '0', STR_PAD_LEFT ), $decimalPlaces, 1 ) );
|
||||
if ( $numberToExamine >= 5 )
|
||||
{
|
||||
$round = static::ROUND_UP;
|
||||
@@ -467,18 +477,18 @@ class _Number implements \JsonSerializable
|
||||
}
|
||||
|
||||
/* Start with a number without the decimal point, truncated where we need it */
|
||||
$abs = "{$this->beforeDecimalPoint}" . str_pad( \substr( str_pad( $this->afterDecimalPoint, $this->numberOfDecimalPlaces, '0', STR_PAD_LEFT ), 0, $decimalPlaces ), $decimalPlaces, '0', STR_PAD_LEFT );
|
||||
$abs = "{$this->beforeDecimalPoint}" . str_pad( substr( str_pad( $this->afterDecimalPoint, $this->numberOfDecimalPlaces, '0', STR_PAD_LEFT ), 0, $decimalPlaces ), $decimalPlaces, '0', STR_PAD_LEFT );
|
||||
|
||||
/* If we're going up, add one on */
|
||||
if ( $round === static::ROUND_UP )
|
||||
{
|
||||
$abs = \intval( $abs ) + 1;
|
||||
$abs = intval( $abs ) + 1;
|
||||
}
|
||||
|
||||
/* Put the decimal back */
|
||||
if ( $decimalPlaces )
|
||||
{
|
||||
return new static( ( $this->positive === FALSE ? "-" : '' ) . \substr( $abs, 0, -$decimalPlaces ) . '.' . str_pad( \substr( $abs, -$decimalPlaces ), $decimalPlaces, '0', STR_PAD_LEFT ) );
|
||||
return new static( ( $this->positive === FALSE ? "-" : '' ) . substr( $abs, 0, -$decimalPlaces ) . '.' . str_pad( substr( $abs, -$decimalPlaces ), $decimalPlaces, '0', STR_PAD_LEFT ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -489,10 +499,10 @@ class _Number implements \JsonSerializable
|
||||
/**
|
||||
* Calculate percentage
|
||||
*
|
||||
* @param int|\IPS\Math\Number $percentage The percentage
|
||||
* @return \IPS\Math\Number
|
||||
* @param int|Number $percentage The percentage
|
||||
* @return Number
|
||||
*/
|
||||
public function percentage( $percentage )
|
||||
public function percentage( Number|int $percentage ): Number
|
||||
{
|
||||
if ( !( $percentage instanceof static ) )
|
||||
{
|
||||
@@ -505,11 +515,11 @@ class _Number implements \JsonSerializable
|
||||
/**
|
||||
* Get absolute value
|
||||
*
|
||||
* @return \IPS\Math\Number
|
||||
* @return Number
|
||||
*/
|
||||
public function absolute()
|
||||
public function absolute(): Number // we need the underscore version as long as we have monkey patching
|
||||
{
|
||||
return $this->positive ? $this : $this->multiply( new \IPS\Math\Number('-1') );
|
||||
return $this->positive ? $this : $this->multiply( new Number('-1') );
|
||||
}
|
||||
|
||||
/* !Array math */
|
||||
@@ -518,9 +528,9 @@ class _Number implements \JsonSerializable
|
||||
* Get sum of numbers
|
||||
*
|
||||
* @param array $numbers array of \IPS\Math\Number objects
|
||||
* @return \IPS\Math\Number
|
||||
* @return Number
|
||||
*/
|
||||
public static function sum( array $numbers )
|
||||
public static function sum( array $numbers ): Number
|
||||
{
|
||||
$result = new static('0');
|
||||
foreach ( $numbers as $number )
|
||||
@@ -529,14 +539,14 @@ class _Number implements \JsonSerializable
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get product of numbers
|
||||
*
|
||||
* @param array $numbers array of \IPS\Math\Number objects
|
||||
* @return \IPS\Math\Number
|
||||
* @param array $numbers array of \IPS\Math\Number objects
|
||||
* @return Number
|
||||
*/
|
||||
public static function product( array $numbers )
|
||||
public static function product( array $numbers ): Number
|
||||
{
|
||||
$result = new static('0');
|
||||
foreach ( $numbers as $number )
|
||||
@@ -552,11 +562,11 @@ class _Number implements \JsonSerializable
|
||||
* Normalise two numbers
|
||||
* Makes the number of decimal places for both numbers equal so that math can be done on them
|
||||
*
|
||||
* @param \IPS\Math\Number $number1 Number 1
|
||||
* @param \IPS\Math\Number $number2 Number 2
|
||||
* @param Number $number1 Number 1
|
||||
* @param Number $number2 Number 2
|
||||
* @return array
|
||||
*/
|
||||
protected static function _normaliseTwoNumbers( Number $number1, Number $number2 )
|
||||
protected static function _normaliseTwoNumbers( Number $number1, Number $number2 ): array
|
||||
{
|
||||
$number1 = clone $number1;
|
||||
$number2 = clone $number2;
|
||||
@@ -579,10 +589,10 @@ class _Number implements \JsonSerializable
|
||||
/**
|
||||
* Makes the number of decimal places a specific number
|
||||
*
|
||||
* @param int $to The number of decimal places
|
||||
* @param int $to The number of decimal places
|
||||
* @return void
|
||||
*/
|
||||
protected function _normaliseDecimalPlaces( $to )
|
||||
protected function _normaliseDecimalPlaces( int $to ) : void
|
||||
{
|
||||
$this->afterDecimalPoint *= ( ( $to - $this->numberOfDecimalPlaces ) * 10 );
|
||||
$this->numberOfDecimalPlaces = $to;
|
||||
@@ -593,7 +603,7 @@ class _Number implements \JsonSerializable
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _simplifyDecimalPlaces()
|
||||
protected function _simplifyDecimalPlaces() : void
|
||||
{
|
||||
if ( $this->afterDecimalPoint === 0 )
|
||||
{
|
||||
@@ -631,7 +641,7 @@ class _Number implements \JsonSerializable
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
public function jsonSerialize(): string
|
||||
{
|
||||
return (string) $this;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user