Version 5.0.0 beta 1

This commit is contained in:
Neo committed 2025-12-19 16:27:35 -08:00
1 parent 25ddeb65d6
commit 15c7beabc5
6736 files changed
+627902 -497943

No files matched your search

+84 -74
View File
@@ -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;
}