From 50e42c87ca508d1133728e0fe0040dbb72ce99b8 Mon Sep 17 00:00:00 2001 From: Bob Mulder Date: Wed, 20 Jan 2021 14:48:57 +0100 Subject: [PATCH 1/5] Add round, ceil, floor methods + improve README.md --- README.md | 17 ++++++++++++++++ src/AbstractNumber.php | 45 ++++++++++++++++++++++++++++++++++++++++++ tests/NumberTest.php | 42 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+) diff --git a/README.md b/README.md index 75b991a..c0e3010 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ to the desired needs of your business. - [Multiply](#multiply) - [Modulus](#modulus) - [State & Comparison](#state--comparison) + - [Rounding](#rounding) - [Immutable & Chaining](#immutable--chaining) - [Extensibility](#extensibility) - [Testing](#testing--php-cs-fixer) @@ -164,6 +165,22 @@ $number->isEqual('200'); $number->isZero(); ``` +### Rounding +To round the current number instance, the following methods are available: + +``` php +$number = new Number('200.5000'); + +// rounds the number to '201.0000' +$number->round(); + +// ceils the number to '201.0000' +$number->ceil(); + +// floors the number to '200.0000' +$number->floor(); +``` + ### Immutable & Chaining Since the `Number` class is immutable, most methods will return a new `Number` instance. diff --git a/src/AbstractNumber.php b/src/AbstractNumber.php index 5fecd00..9b62383 100644 --- a/src/AbstractNumber.php +++ b/src/AbstractNumber.php @@ -321,6 +321,51 @@ public function isEqual($value, int $scale = null): bool return bccomp($this->value, $number->get(), $scale) === 0; } + /** + * Rounds the current number, with a given precision (default 0). + * + * @param int $precision + * @return $this + */ + public function round(int $precision = 0): self + { + if ($this->isNegative()) { + return $this->subtract('0.' . str_repeat('0', $precision) . '5', $precision); + } + + return $this->add('0.' . str_repeat('0', $precision) . '5', $precision); + } + + /** + * Ceils the current number. + * + * @return $this + */ + public function ceil(): self + { + $result = 1; + if (static::isNegative()) { + --$result; + } + + return $this->add($result, 0); + } + + /** + * Floors the current number. + * + * @return $this + */ + public function floor(): self + { + $result = 0; + if (static::isNegative()) { + --$result; + } + + return $this->add($result, 0); + } + /** * Returns it's parent by which this instance was initialized. */ diff --git a/tests/NumberTest.php b/tests/NumberTest.php index df29030..c648b4b 100644 --- a/tests/NumberTest.php +++ b/tests/NumberTest.php @@ -522,6 +522,48 @@ public function testIsEqual(): void $this->assertFalse($five->isEqual('5.0001')); } + public function testRound(): void + { + $number = new Number('4.9000'); + $this->assertEquals('5', $number->round(0)); + + $number = new Number('4.1000'); + $this->assertEquals('4', $number->round(0)); + + $number = new Number('4.4900'); + $this->assertEquals('4', $number->round(0)); + + $number = new Number('4.5000'); + $this->assertEquals('5', $number->round(0)); + + $number = new Number('4.5100'); + $this->assertEquals('5', $number->round(0)); + + $number = new Number('4.4720'); + $this->assertEquals('4.4700', $number->round(2)); + + $number = new Number('4.4770'); + $this->assertEquals('4.4800', $number->round(2)); + + // $number = new Number('4200.0000'); + // $this->assertEquals('4000.0000', $number->round(-3)); + + } + + public function testCeil(): void + { + $number = new Number('4.1000'); + + $this->assertEquals('5.0000', $number->ceil()->toString()); + } + + public function testFloor(): void + { + $number = new Number('4.9000'); + + $this->assertEquals('4.0000', $number->floor()->toString()); + } + public function testCanTraceByParent(): void { $five = new Number(5); From c05b275da2e23fd1ec871f7666cdd90483585a96 Mon Sep 17 00:00:00 2001 From: bobmulder Date: Wed, 20 Jan 2021 13:49:32 +0000 Subject: [PATCH 2/5] Fix styling --- tests/NumberTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/NumberTest.php b/tests/NumberTest.php index c648b4b..395d41c 100644 --- a/tests/NumberTest.php +++ b/tests/NumberTest.php @@ -547,7 +547,6 @@ public function testRound(): void // $number = new Number('4200.0000'); // $this->assertEquals('4000.0000', $number->round(-3)); - } public function testCeil(): void From 5c5bb31fce58b615385375c4d2678675ba6694be Mon Sep 17 00:00:00 2001 From: jonmldr Date: Wed, 20 Jan 2021 23:34:09 +0100 Subject: [PATCH 3/5] Improved round, ceil & floor logic, added unit tests --- src/AbstractNumber.php | 39 +++++++++---------- .../InvalidRoundingModeException.php | 15 +++++++ tests/NumberTest.php | 15 +++++-- 3 files changed, 44 insertions(+), 25 deletions(-) create mode 100644 src/Exception/InvalidRoundingModeException.php diff --git a/src/AbstractNumber.php b/src/AbstractNumber.php index 9b62383..3f6ddcd 100644 --- a/src/AbstractNumber.php +++ b/src/AbstractNumber.php @@ -7,12 +7,24 @@ use Number\Exception\DecimalExponentError; use Number\Exception\DivisionByZeroError; use Number\Exception\InvalidNumberInputTypeException; +use Number\Exception\InvalidRoundingModeException; abstract class AbstractNumber { protected const INTERNAL_SCALE = 12; protected const DEFAULT_SCALE = 4; + public const ROUND_HALF_UP = 1; + public const ROUND_HALF_DOWN = 2; + public const ROUND_HALF_EVEN = 3; + public const ROUND_HALF_ODD = 4; + private const ROUNDING_MODES = [ + self::ROUND_HALF_UP => self::ROUND_HALF_UP, + self::ROUND_HALF_DOWN => self::ROUND_HALF_DOWN, + self::ROUND_HALF_EVEN => self::ROUND_HALF_EVEN, + self::ROUND_HALF_ODD => self::ROUND_HALF_ODD, + ]; + protected string $value; protected ?self $parent; @@ -325,45 +337,30 @@ public function isEqual($value, int $scale = null): bool * Rounds the current number, with a given precision (default 0). * * @param int $precision - * @return $this */ - public function round(int $precision = 0): self + public function round(int $precision = 0, int $mode = self::ROUND_HALF_UP): self { - if ($this->isNegative()) { - return $this->subtract('0.' . str_repeat('0', $precision) . '5', $precision); + if (in_array($mode, self::ROUNDING_MODES) === false) { + throw new InvalidRoundingModeException(); } - return $this->add('0.' . str_repeat('0', $precision) . '5', $precision); + return $this->init((string) round((float) $this->value, $precision, $mode)); } /** * Ceils the current number. - * - * @return $this */ public function ceil(): self { - $result = 1; - if (static::isNegative()) { - --$result; - } - - return $this->add($result, 0); + return $this->init((string) ceil((float) $this->value)); } /** * Floors the current number. - * - * @return $this */ public function floor(): self { - $result = 0; - if (static::isNegative()) { - --$result; - } - - return $this->add($result, 0); + return $this->init((string) floor((float) $this->value)); } /** diff --git a/src/Exception/InvalidRoundingModeException.php b/src/Exception/InvalidRoundingModeException.php new file mode 100644 index 0000000..42869b6 --- /dev/null +++ b/src/Exception/InvalidRoundingModeException.php @@ -0,0 +1,15 @@ +assertEquals('4.4800', $number->round(2)); - // $number = new Number('4200.0000'); - // $this->assertEquals('4000.0000', $number->round(-3)); + $number = new Number('4200.0000'); + $this->assertEquals('4000.0000', $number->round(-3)); + + $number = new Number('46000.0000'); + $this->assertEquals('50000.0000', $number->round(-4)); } public function testCeil(): void { $number = new Number('4.1000'); - $this->assertEquals('5.0000', $number->ceil()->toString()); + + $number = new Number('-4.1000'); + $this->assertEquals('-4.0000', $number->ceil()->toString()); } public function testFloor(): void { $number = new Number('4.9000'); - $this->assertEquals('4.0000', $number->floor()->toString()); + + $number = new Number('-4.9000'); + $this->assertEquals('-5.0000', $number->floor()->toString()); } public function testCanTraceByParent(): void From 97cf1c27c1c98eeee0c679ca59203aff7d338db2 Mon Sep 17 00:00:00 2001 From: jonmldr Date: Wed, 20 Jan 2021 23:47:38 +0100 Subject: [PATCH 4/5] Improved unit tests when Intl extension is not loaded, updated changelog --- CHANGELOG.md | 4 ++++ tests/AbstractNumberImplementationTest.php | 6 ++++++ tests/NumberTest.php | 6 ++++++ 3 files changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f7c26c..f6f4efe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to `php-number` will be documented in this file +## 1.3.0 +- Added `round`, `ceil` & `floor` method in `AbstractNumber` +- Improved unit tests when Intl extension is not loaded + ## 1.2.0 - 2021-01-20 - Added PHP 8 support + type fix ([#8](https://github.com/madebybob/php-number/pull/8) by [@affektde](https://github.com/affektde)) diff --git a/tests/AbstractNumberImplementationTest.php b/tests/AbstractNumberImplementationTest.php index e19b6e6..ec9f70d 100644 --- a/tests/AbstractNumberImplementationTest.php +++ b/tests/AbstractNumberImplementationTest.php @@ -28,6 +28,12 @@ public function testCanInitializeAbstractNumberImplementation(): void public function testFormatAbstractNumberImplementation(): void { + if (extension_loaded('intl') === false) { + $this->markTestSkipped('Intl extension not loaded.'); + + return; + } + Locale::setDefault('nl_NL'); $money = new Money('9342.1539'); diff --git a/tests/NumberTest.php b/tests/NumberTest.php index 30c6b24..4c3b292 100644 --- a/tests/NumberTest.php +++ b/tests/NumberTest.php @@ -588,6 +588,12 @@ public function testCanTraceByParent(): void public function testFormatNumber(): void { + if (extension_loaded('intl') === false) { + $this->markTestSkipped('Intl extension not loaded.'); + + return; + } + Locale::setDefault('nl_NL'); // Round up, default 4 fraction digits From 8ec934c9dd257ed51b42cd257af4e9c1b351ce40 Mon Sep 17 00:00:00 2001 From: Bob Mulder Date: Thu, 21 Jan 2021 08:56:19 +0100 Subject: [PATCH 5/5] Improve tests for round, ceil and floor --- src/AbstractNumber.php | 2 -- tests/NumberTest.php | 57 ++++++++++++++++++++++-------------------- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/src/AbstractNumber.php b/src/AbstractNumber.php index 3f6ddcd..46a3362 100644 --- a/src/AbstractNumber.php +++ b/src/AbstractNumber.php @@ -335,8 +335,6 @@ public function isEqual($value, int $scale = null): bool /** * Rounds the current number, with a given precision (default 0). - * - * @param int $precision */ public function round(int $precision = 0, int $mode = self::ROUND_HALF_UP): self { diff --git a/tests/NumberTest.php b/tests/NumberTest.php index 4c3b292..ae13729 100644 --- a/tests/NumberTest.php +++ b/tests/NumberTest.php @@ -524,50 +524,53 @@ public function testIsEqual(): void public function testRound(): void { - $number = new Number('4.9000'); - $this->assertEquals('5', $number->round(0)); + $this->assertEquals('5.0000', Number::create('4.900')->round(0)->toString()); + $this->assertEquals('4.0000', Number::create('4.1000')->round(0)->toString()); + $this->assertEquals('4.0000', Number::create('4.4900')->round(0)->toString()); + $this->assertEquals('5.0000', Number::create('4.5000')->round(0)->toString()); + $this->assertEquals('5.0000', Number::create('4.5100')->round(0)->toString()); - $number = new Number('4.1000'); - $this->assertEquals('4', $number->round(0)); + $this->assertEquals('4.4700', Number::create('4.4720')->round(2)->toString()); + $this->assertEquals('4.4800', Number::create('4.4770')->round(2)->toString()); - $number = new Number('4.4900'); - $this->assertEquals('4', $number->round(0)); + $this->assertEquals('8.4780', Number::create('8.4776')->round(3)->toString()); + $this->assertEquals('8.4770', Number::create('8.4772')->round(3)->toString()); - $number = new Number('4.5000'); - $this->assertEquals('5', $number->round(0)); + $this->assertEquals('4000.0000', Number::create('4200.0000')->round(-3)->toString()); + $this->assertEquals('50000.0000', Number::create('46000.0000')->round(-4)->toString()); - $number = new Number('4.5100'); - $this->assertEquals('5', $number->round(0)); + $this->assertEquals('-5.0000', Number::create('-4.900')->round(0)->toString()); + $this->assertEquals('-4.0000', Number::create('-4.1000')->round(0)->toString()); + $this->assertEquals('-4.0000', Number::create('-4.4900')->round(0)->toString()); + $this->assertEquals('-5.0000', Number::create('-4.5000')->round(0)->toString()); + $this->assertEquals('-5.0000', Number::create('-4.5100')->round(0)->toString()); - $number = new Number('4.4720'); - $this->assertEquals('4.4700', $number->round(2)); + $this->assertEquals('-4.4700', Number::create('-4.4720')->round(2)->toString()); + $this->assertEquals('-4.4800', Number::create('-4.4770')->round(2)->toString()); - $number = new Number('4.4770'); - $this->assertEquals('4.4800', $number->round(2)); + $this->assertEquals('-8.4780', Number::create('-8.4776')->round(3)->toString()); + $this->assertEquals('-8.4770', Number::create('-8.4772')->round(3)->toString()); - $number = new Number('4200.0000'); - $this->assertEquals('4000.0000', $number->round(-3)); - - $number = new Number('46000.0000'); - $this->assertEquals('50000.0000', $number->round(-4)); + $this->assertEquals('-4000.0000', Number::create('-4200.0000')->round(-3)->toString()); + $this->assertEquals('-50000.0000', Number::create('-46000.0000')->round(-4)->toString()); } public function testCeil(): void { - $number = new Number('4.1000'); - $this->assertEquals('5.0000', $number->ceil()->toString()); + $this->assertEquals('5.0000', Number::create('4.1000')->ceil()->toString()); + $this->assertEquals('5.0000', Number::create('4.8000')->ceil()->toString()); - $number = new Number('-4.1000'); - $this->assertEquals('-4.0000', $number->ceil()->toString()); + $this->assertEquals('-4.0000', Number::create('-4.1000')->ceil()->toString()); + $this->assertEquals('-4.0000', Number::create('-4.8000')->ceil()->toString()); } public function testFloor(): void { - $number = new Number('4.9000'); - $this->assertEquals('4.0000', $number->floor()->toString()); + $this->assertEquals('4.0000', Number::create('4.9000')->floor()->toString()); + $this->assertEquals('4.0000', Number::create('4.1000')->floor()->toString()); - $number = new Number('-4.9000'); - $this->assertEquals('-5.0000', $number->floor()->toString()); + $this->assertEquals('-5.0000', Number::create('-4.9000')->floor()->toString()); + $this->assertEquals('-5.0000', Number::create('-4.1000')->floor()->toString()); } public function testCanTraceByParent(): void