Skip to content

Commit

Permalink
Add round, ceil, floor methods
Browse files Browse the repository at this point in the history
  • Loading branch information
bobmulder authored Jan 21, 2021
2 parents 90cd35b + 8ec934c commit 96b936d
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.

Expand Down
40 changes: 40 additions & 0 deletions src/AbstractNumber.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -321,6 +333,34 @@ 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).
*/
public function round(int $precision = 0, int $mode = self::ROUND_HALF_UP): self
{
if (in_array($mode, self::ROUNDING_MODES) === false) {
throw new InvalidRoundingModeException();
}

return $this->init((string) round((float) $this->value, $precision, $mode));
}

/**
* Ceils the current number.
*/
public function ceil(): self
{
return $this->init((string) ceil((float) $this->value));
}

/**
* Floors the current number.
*/
public function floor(): self
{
return $this->init((string) floor((float) $this->value));
}

/**
* Returns it's parent by which this instance was initialized.
*/
Expand Down
15 changes: 15 additions & 0 deletions src/Exception/InvalidRoundingModeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Number\Exception;

use InvalidArgumentException;

class InvalidRoundingModeException extends InvalidArgumentException
{
public function __construct()
{
parent::__construct('Invalid rounding mode given.');
}
}
6 changes: 6 additions & 0 deletions tests/AbstractNumberImplementationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
57 changes: 57 additions & 0 deletions tests/NumberTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,57 @@ public function testIsEqual(): void
$this->assertFalse($five->isEqual('5.0001'));
}

public function testRound(): void
{
$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());

$this->assertEquals('4.4700', Number::create('4.4720')->round(2)->toString());
$this->assertEquals('4.4800', Number::create('4.4770')->round(2)->toString());

$this->assertEquals('8.4780', Number::create('8.4776')->round(3)->toString());
$this->assertEquals('8.4770', Number::create('8.4772')->round(3)->toString());

$this->assertEquals('4000.0000', Number::create('4200.0000')->round(-3)->toString());
$this->assertEquals('50000.0000', Number::create('46000.0000')->round(-4)->toString());

$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());

$this->assertEquals('-4.4700', Number::create('-4.4720')->round(2)->toString());
$this->assertEquals('-4.4800', Number::create('-4.4770')->round(2)->toString());

$this->assertEquals('-8.4780', Number::create('-8.4776')->round(3)->toString());
$this->assertEquals('-8.4770', Number::create('-8.4772')->round(3)->toString());

$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
{
$this->assertEquals('5.0000', Number::create('4.1000')->ceil()->toString());
$this->assertEquals('5.0000', Number::create('4.8000')->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
{
$this->assertEquals('4.0000', Number::create('4.9000')->floor()->toString());
$this->assertEquals('4.0000', Number::create('4.1000')->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
{
$five = new Number(5);
Expand All @@ -540,6 +591,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
Expand Down

0 comments on commit 96b936d

Please sign in to comment.