From dcc3f673a17721bba70d1cf13f2185023e81ba6f Mon Sep 17 00:00:00 2001 From: gmitsos Date: Thu, 1 May 2025 00:46:29 +0300 Subject: [PATCH 01/13] Decimal and NonNegativeDecimal Implementation --- src/Number/Decimal.php | 91 ++++++++++++++++++++++++ src/Number/NonNegativeDecimal.php | 62 +++++++++++++++++ tests/Number/DecimalTest.php | 80 +++++++++++++++++++++ tests/Number/NonNegativeDecimalTest.php | 92 +++++++++++++++++++++++++ 4 files changed, 325 insertions(+) create mode 100644 src/Number/Decimal.php create mode 100644 src/Number/NonNegativeDecimal.php create mode 100644 tests/Number/DecimalTest.php create mode 100644 tests/Number/NonNegativeDecimalTest.php diff --git a/src/Number/Decimal.php b/src/Number/Decimal.php new file mode 100644 index 0000000..f50e8d5 --- /dev/null +++ b/src/Number/Decimal.php @@ -0,0 +1,91 @@ + + * @param int $rounding (1 : PHP_ROUND_HALF_UP (Default), 2 : PHP_ROUND_HALF_DOWN, 3 : PHP_ROUND_HALF_EVEN, 4 : PHP_ROUND_HALF_ODD) + */ + public function __construct(float $value, int $precision = 2, int $rounding = PHP_ROUND_HALF_UP) + { + if ($value == 0.0) { + $minimumPrecision = 0; + } else { + $minimumPrecision = -(floor(log10(abs($value))) + 1); + } + + if ($precision > 16 || $precision <= $minimumPrecision) { + throw new InvalidArgumentException("Precision must be greater than {$minimumPrecision} and less than or equal to 16."); + } + + if ($rounding < 1 || $rounding > 4) { + throw new InvalidArgumentException('Rounding must be between 1 and 4.'); + } + + $this->value = round($value, $precision, $rounding); + $this->precision = $precision; + $this->rounding = $rounding; + } + + final public function getValue(): float + { + return $this->value; + } + + final public function getPrecision(): int + { + return $this->precision; + } + + final public function getRounding(): int + { + return $this->rounding; + } + + /** + * @param int|float|string $value + * @param int $precision + * @param int $rounding (1 : PHP_ROUND_HALF_UP, 2 : PHP_ROUND_HALF_DOWN, 3 : PHP_ROUND_HALF_EVEN, 4 : PHP_ROUND_HALF_ODD) + * @return self + */ + public static function fromNumeric($value, int $precision = 2, int $rounding = PHP_ROUND_HALF_UP): self + { + + if (!is_numeric($value)) { + throw new InvalidArgumentException('Value is not numeric.'); + } + + if ((float)$value == 0.0) { + $minimumPrecision = 0; + } else { + $minimumPrecision = -(floor(log10(abs((float)$value))) + 1); + } + + if ($precision > 16 || $precision <= $minimumPrecision) { + throw new InvalidArgumentException("Precision must be greater than {$minimumPrecision} and less than or equal to 16."); + } + + if ($rounding < 1 || $rounding > 4) { + throw new InvalidArgumentException('Rounding must be between 1 and 4.'); + } + + $value = round((float)$value, $precision, $rounding); + return new self($value, $precision, $rounding); + + } + +} diff --git a/src/Number/NonNegativeDecimal.php b/src/Number/NonNegativeDecimal.php new file mode 100644 index 0000000..1b3084a --- /dev/null +++ b/src/Number/NonNegativeDecimal.php @@ -0,0 +1,62 @@ + + * @param int $rounding (1 : PHP_ROUND_HALF_UP (Default), 2 : PHP_ROUND_HALF_DOWN, 3 : PHP_ROUND_HALF_EVEN, 4 : PHP_ROUND_HALF_ODD) + */ + public function __construct(float $value, int $precision = 2, int $rounding = PHP_ROUND_HALF_UP) + { + if ($value < 0) { + throw new InvalidArgumentException('Value must be a non-negative decimal number.'); + } + + parent::__construct($value, $precision, $rounding); + } + + /** + * @param int|float|string $value + * @param int $precision + * @param int $rounding (1 : PHP_ROUND_HALF_UP, 2 : PHP_ROUND_HALF_DOWN, 3 : PHP_ROUND_HALF_EVEN, 4 : PHP_ROUND_HALF_ODD) + * @return self + */ + public static function fromNumeric($value, int $precision = 2, int $rounding = PHP_ROUND_HALF_UP): self + { + if (!is_numeric($value)) { + throw new InvalidArgumentException('Value is not numeric.'); + } + + $value = (float)$value; + + if ($value < 0) { + throw new InvalidArgumentException('Value must be a non-negative decimal number.'); + } + + //parent::fromNumeric($value, $precision, $rounding); would be cleaner and DRY but redoes is_numeric check + + if ((float)$value == 0.0) { + $minimumPrecision = 0; + } else { + $minimumPrecision = -(floor(log10(abs((float)$value))) + 1); + } + + if ($precision > 16 || $precision <= $minimumPrecision) { + throw new InvalidArgumentException("Precision must be > {$minimumPrecision} and <= 16."); + } + + if ($rounding < 1 || $rounding > 4) { + throw new InvalidArgumentException('Rounding must be between 1 and 4.'); + } + + $value = round((float)$value, $precision, $rounding); + return new self($value, $precision, $rounding); + } +} diff --git a/tests/Number/DecimalTest.php b/tests/Number/DecimalTest.php new file mode 100644 index 0000000..99e8955 --- /dev/null +++ b/tests/Number/DecimalTest.php @@ -0,0 +1,80 @@ +getValue(); + + $this->assertEqualsWithDelta(12.3, $result, 0.00001); + } + + public function test_canBeCreatedFromFloatString(): void + { + $result = Decimal::fromNumeric('12.345', 2)->getValue(); + + $this->assertEqualsWithDelta(12.35, $result, 0.00001); + } + + public function test_canBeCreatedFromInteger(): void + { + $result = Decimal::fromNumeric(12.3)->getValue(); + + $this->assertEqualsWithDelta(12.3, $result, 0.00001); + } + + public function test_canNotBeCreatedFromNonNumeric(): void + { + $this->expectException(InvalidArgumentException::class); + + Decimal::fromNumeric('abc'); + } + + public function test_IsEqualToSameTypeWithSameValue(): void + { + $float1 = new Decimal(12.345); + $float2 = new Decimal(12.345); + + $this->assertTrue($float1->equals($float2)); + $this->assertTrue($float2->equals($float1)); + } + + public function test_IsNotEqualToSameTypeWithDifferentValue(): void + { + $float1 = new Decimal(12.345, 3); + $float2 = new Decimal(12.346, 3); + + $this->assertFalse($float1->equals($float2)); + $this->assertFalse($float2->equals($float1)); + } + + public function test_IsNotEqualToSameTypeWithDifferentType(): void + { + $float1 = new Decimal(12, 0); + $integer = new Integer(12); + + $this->assertFalse($float1->equals($integer)); + $this->assertFalse($integer->equals($float1)); + } + + public function test_GetMethods(): void + { + $decimal = new Decimal(12.345, 2 , PHP_ROUND_HALF_UP); + + $this->assertEquals(12.35, $decimal->getValue()); + $this->assertEquals(2, $decimal->getPrecision()); + $this->assertEquals(1, $decimal->getRounding()); + } +} diff --git a/tests/Number/NonNegativeDecimalTest.php b/tests/Number/NonNegativeDecimalTest.php new file mode 100644 index 0000000..efa3405 --- /dev/null +++ b/tests/Number/NonNegativeDecimalTest.php @@ -0,0 +1,92 @@ +getValue(); + + $this->assertEqualsWithDelta(12.34, $result, 0.00001); + } + + public function test_CanBeCreatedFromZero(): void + { + $result = (new NonNegativeDecimal(0))->getValue(); + + $this->assertEqualsWithDelta(0.0, $result, 0.00001); + } + + public function test_canBeCreatedFromNumericString(): void + { + $result = NonNegativeDecimal::fromNumeric('12.34')->getValue(); + + $this->assertEqualsWithDelta(12.34, $result, 0.00001); + } + + public function test_canBeCreatedFromFloat(): void + { + $result = NonNegativeDecimal::fromNumeric(12.34)->getValue(); + + $this->assertEqualsWithDelta(12.34, $result, 0.00001); + } + + public function test_canNotBeCreatedFromNegativeUsingNumericFloat(): void + { + $this->expectException(InvalidArgumentException::class); + + NonNegativeDecimal::fromNumeric(-12.34); + } + + public function test_canNotBeCreatedFromNonNumeric(): void + { + $this->expectException(InvalidArgumentException::class); + + NonNegativeDecimal::fromNumeric('abc'); + } + + public function test_CannotBeCreatedFromNegativeFloat(): void + { + $this->expectException(InvalidArgumentException::class); + + new NonNegativeDecimal(-12.34); + } + + public function test_IsEqualToSameTypeWithSameValue(): void + { + $float1 = new NonNegativeDecimal(12.34); + $float2 = new NonNegativeDecimal(12.34); + + $this->assertTrue($float1->equals($float2)); + $this->assertTrue($float2->equals($float1)); + } + + public function test_IsNotEqualToSameTypeWithDifferentValue(): void + { + $float1 = new NonNegativeDecimal(12.34); + $float2 = new NonNegativeDecimal(12.35); + + $this->assertFalse($float1->equals($float2)); + $this->assertFalse($float2->equals($float1)); + } + + public function test_isNotEqualToDifferentType(): void + { + $float1 = new NonNegativeDecimal(12.34); + $float2 = new Decimal(12.34); + + $this->assertFalse($float1->equals($float2)); + $this->assertFalse($float2->equals($float1)); + } +} From ecd68b78206b97e58e5b58e93fe30b5b5c0fa8b5 Mon Sep 17 00:00:00 2001 From: gmitsos Date: Thu, 1 May 2025 00:53:47 +0300 Subject: [PATCH 02/13] CS --- src/Number/Decimal.php | 11 ++++++----- tests/Number/DecimalTest.php | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Number/Decimal.php b/src/Number/Decimal.php index f50e8d5..3968b08 100644 --- a/src/Number/Decimal.php +++ b/src/Number/Decimal.php @@ -29,7 +29,9 @@ public function __construct(float $value, int $precision = 2, int $rounding = PH } if ($precision > 16 || $precision <= $minimumPrecision) { - throw new InvalidArgumentException("Precision must be greater than {$minimumPrecision} and less than or equal to 16."); + throw new InvalidArgumentException( + "Precision must be greater than {$minimumPrecision} and less than or equal to 16." + ); } if ($rounding < 1 || $rounding > 4) { @@ -64,7 +66,6 @@ final public function getRounding(): int */ public static function fromNumeric($value, int $precision = 2, int $rounding = PHP_ROUND_HALF_UP): self { - if (!is_numeric($value)) { throw new InvalidArgumentException('Value is not numeric.'); } @@ -76,7 +77,9 @@ public static function fromNumeric($value, int $precision = 2, int $rounding = P } if ($precision > 16 || $precision <= $minimumPrecision) { - throw new InvalidArgumentException("Precision must be greater than {$minimumPrecision} and less than or equal to 16."); + throw new InvalidArgumentException( + "Precision must be greater than {$minimumPrecision} and less than or equal to 16." + ); } if ($rounding < 1 || $rounding > 4) { @@ -85,7 +88,5 @@ public static function fromNumeric($value, int $precision = 2, int $rounding = P $value = round((float)$value, $precision, $rounding); return new self($value, $precision, $rounding); - } - } diff --git a/tests/Number/DecimalTest.php b/tests/Number/DecimalTest.php index 99e8955..2f161e2 100644 --- a/tests/Number/DecimalTest.php +++ b/tests/Number/DecimalTest.php @@ -71,7 +71,7 @@ public function test_IsNotEqualToSameTypeWithDifferentType(): void public function test_GetMethods(): void { - $decimal = new Decimal(12.345, 2 , PHP_ROUND_HALF_UP); + $decimal = new Decimal(12.345, 2, PHP_ROUND_HALF_UP); $this->assertEquals(12.35, $decimal->getValue()); $this->assertEquals(2, $decimal->getPrecision()); From 796f5a840aa6f1711ad0f933ccbf701d5c4a896c Mon Sep 17 00:00:00 2001 From: gmitsos Date: Thu, 1 May 2025 00:59:43 +0300 Subject: [PATCH 03/13] psalm --- src/Number/NonNegativeDecimal.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Number/NonNegativeDecimal.php b/src/Number/NonNegativeDecimal.php index 1b3084a..ec4a0a7 100644 --- a/src/Number/NonNegativeDecimal.php +++ b/src/Number/NonNegativeDecimal.php @@ -42,10 +42,10 @@ public static function fromNumeric($value, int $precision = 2, int $rounding = P //parent::fromNumeric($value, $precision, $rounding); would be cleaner and DRY but redoes is_numeric check - if ((float)$value == 0.0) { + if ($value == 0.0) { $minimumPrecision = 0; } else { - $minimumPrecision = -(floor(log10(abs((float)$value))) + 1); + $minimumPrecision = -(floor(log10(abs($value))) + 1); } if ($precision > 16 || $precision <= $minimumPrecision) { @@ -56,7 +56,7 @@ public static function fromNumeric($value, int $precision = 2, int $rounding = P throw new InvalidArgumentException('Rounding must be between 1 and 4.'); } - $value = round((float)$value, $precision, $rounding); + $value = round($value, $precision, $rounding); return new self($value, $precision, $rounding); } } From e7e581f7393b1ebfa79c7247db938c6e6f005e8a Mon Sep 17 00:00:00 2001 From: gmitsos Date: Thu, 1 May 2025 01:05:57 +0300 Subject: [PATCH 04/13] CS --- src/Number/Decimal.php | 6 ++++-- src/Number/NonNegativeDecimal.php | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Number/Decimal.php b/src/Number/Decimal.php index 3968b08..15183f6 100644 --- a/src/Number/Decimal.php +++ b/src/Number/Decimal.php @@ -18,7 +18,8 @@ class Decimal extends AbstractType /** * @param float $value * @param int $precision < max : 16> - * @param int $rounding (1 : PHP_ROUND_HALF_UP (Default), 2 : PHP_ROUND_HALF_DOWN, 3 : PHP_ROUND_HALF_EVEN, 4 : PHP_ROUND_HALF_ODD) + * @param int $rounding (1 : PHP_ROUND_HALF_UP (Default), 2 : PHP_ROUND_HALF_DOWN, + * 3 : PHP_ROUND_HALF_EVEN, 4 : PHP_ROUND_HALF_ODD) */ public function __construct(float $value, int $precision = 2, int $rounding = PHP_ROUND_HALF_UP) { @@ -61,7 +62,8 @@ final public function getRounding(): int /** * @param int|float|string $value * @param int $precision - * @param int $rounding (1 : PHP_ROUND_HALF_UP, 2 : PHP_ROUND_HALF_DOWN, 3 : PHP_ROUND_HALF_EVEN, 4 : PHP_ROUND_HALF_ODD) + * @param int $rounding (1 : PHP_ROUND_HALF_UP, 2 : PHP_ROUND_HALF_DOWN, + * 3 : PHP_ROUND_HALF_EVEN, 4 : PHP_ROUND_HALF_ODD) * @return self */ public static function fromNumeric($value, int $precision = 2, int $rounding = PHP_ROUND_HALF_UP): self diff --git a/src/Number/NonNegativeDecimal.php b/src/Number/NonNegativeDecimal.php index ec4a0a7..661cdff 100644 --- a/src/Number/NonNegativeDecimal.php +++ b/src/Number/NonNegativeDecimal.php @@ -11,7 +11,8 @@ class NonNegativeDecimal extends Decimal /** * @param float $value * @param int $precision < max : 16> - * @param int $rounding (1 : PHP_ROUND_HALF_UP (Default), 2 : PHP_ROUND_HALF_DOWN, 3 : PHP_ROUND_HALF_EVEN, 4 : PHP_ROUND_HALF_ODD) + * @param int $rounding (1 : PHP_ROUND_HALF_UP (Default), 2 : PHP_ROUND_HALF_DOWN, + * 3 : PHP_ROUND_HALF_EVEN, 4 : PHP_ROUND_HALF_ODD) */ public function __construct(float $value, int $precision = 2, int $rounding = PHP_ROUND_HALF_UP) { @@ -25,7 +26,8 @@ public function __construct(float $value, int $precision = 2, int $rounding = PH /** * @param int|float|string $value * @param int $precision - * @param int $rounding (1 : PHP_ROUND_HALF_UP, 2 : PHP_ROUND_HALF_DOWN, 3 : PHP_ROUND_HALF_EVEN, 4 : PHP_ROUND_HALF_ODD) + * @param int $rounding (1 : PHP_ROUND_HALF_UP, 2 : PHP_ROUND_HALF_DOWN, + * 3 : PHP_ROUND_HALF_EVEN, 4 : PHP_ROUND_HALF_ODD) * @return self */ public static function fromNumeric($value, int $precision = 2, int $rounding = PHP_ROUND_HALF_UP): self From f77bdb10e15dbe47542f2a664e99dcef9422d9b2 Mon Sep 17 00:00:00 2001 From: gmitsos Date: Thu, 8 May 2025 12:28:54 +0300 Subject: [PATCH 05/13] Update test --- tests/Number/DecimalTest.php | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/tests/Number/DecimalTest.php b/tests/Number/DecimalTest.php index 2f161e2..9176e9d 100644 --- a/tests/Number/DecimalTest.php +++ b/tests/Number/DecimalTest.php @@ -14,6 +14,13 @@ */ final class DecimalTest extends TestCase { + public function test_CanBeCreatedFromFloatWithDefaultPrecision(): void + { + $result = (new Decimal(12.3456))->getValue(); + + $this->assertEqualsWithDelta(12.35, $result, 0.00001); + } + public function test_CanBeCreatedFromFloat(): void { $result = (new Decimal(12.345, 1))->getValue(); @@ -23,7 +30,7 @@ public function test_CanBeCreatedFromFloat(): void public function test_canBeCreatedFromFloatString(): void { - $result = Decimal::fromNumeric('12.345', 2)->getValue(); + $result = Decimal::fromNumeric('12.345')->getValue(); $this->assertEqualsWithDelta(12.35, $result, 0.00001); } @@ -77,4 +84,23 @@ public function test_GetMethods(): void $this->assertEquals(2, $decimal->getPrecision()); $this->assertEquals(1, $decimal->getRounding()); } + + public function test_RoundingValues(): void + { + $decimal = new Decimal(12.345, 2, PHP_ROUND_HALF_UP); + $this->assertEquals(12.35, $decimal->getValue()); + $this->assertEquals(1, $decimal->getRounding()); + + $decimal = new Decimal(12.345, 2, PHP_ROUND_HALF_DOWN); + $this->assertEquals(12.34, $decimal->getValue()); + $this->assertEquals(2, $decimal->getRounding()); + + $decimal = new Decimal(12.345, 2, PHP_ROUND_HALF_EVEN); + $this->assertEquals(12.34, $decimal->getValue()); + $this->assertEquals(3, $decimal->getRounding()); + + $decimal = new Decimal(12.345, 2, PHP_ROUND_HALF_ODD); + $this->assertEquals(12.35, $decimal->getValue()); + $this->assertEquals(4, $decimal->getRounding()); + } } From 2fc7185b717991d166b9c4df6e82961ba4a37a0c Mon Sep 17 00:00:00 2001 From: gmitsos Date: Thu, 8 May 2025 12:38:09 +0300 Subject: [PATCH 06/13] cases --- tests/Number/DecimalTest.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/Number/DecimalTest.php b/tests/Number/DecimalTest.php index 9176e9d..3c5233f 100644 --- a/tests/Number/DecimalTest.php +++ b/tests/Number/DecimalTest.php @@ -103,4 +103,28 @@ public function test_RoundingValues(): void $this->assertEquals(12.35, $decimal->getValue()); $this->assertEquals(4, $decimal->getRounding()); } + + public function test_RoundingThrowsExceptionOnInvalidHigherValue(): void + { + $this->expectException(InvalidArgumentException::class); + new Decimal(12.345, 2, 5); + } + + public function test_RoundingThrowsExceptionOnInvalidLowerValue(): void + { + $this->expectException(InvalidArgumentException::class); + new Decimal(12.345, 2, 0); + } + + public function test_PrecisionThrowsExceptionWhenTooHigh(): void + { + $this->expectException(InvalidArgumentException::class); + new Decimal(2, 17); + } + + public function test_PrecisionThrowsExceptionWhenTooLow(): void + { + $this->expectException(InvalidArgumentException::class); + new Decimal(2, -1); + } } From 39c70961f53482a736be52acc18257d0821cf701 Mon Sep 17 00:00:00 2001 From: gmitsos Date: Thu, 8 May 2025 12:47:47 +0300 Subject: [PATCH 07/13] cases --- tests/Number/DecimalTest.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/Number/DecimalTest.php b/tests/Number/DecimalTest.php index 3c5233f..48509d6 100644 --- a/tests/Number/DecimalTest.php +++ b/tests/Number/DecimalTest.php @@ -119,12 +119,35 @@ public function test_RoundingThrowsExceptionOnInvalidLowerValue(): void public function test_PrecisionThrowsExceptionWhenTooHigh(): void { $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("Precision must be greater than -1 and less than or equal to 16."); new Decimal(2, 17); } public function test_PrecisionThrowsExceptionWhenTooLow(): void { $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("Precision must be greater than -1 and less than or equal to 16."); new Decimal(2, -1); } + + public function test_FromNumericPrecisionThrowsExceptionWhenTooLow(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("Precision must be greater than -1 and less than or equal to 16."); + Decimal::fromNumeric(2, -1); + } + + public function test_FromNumericPrecisionThrowsExceptionWhenTooHigh(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("Precision must be greater than -1 and less than or equal to 16."); + Decimal::fromNumeric(2, 17); + } + + public function test_FromNumericPrecisionThrowsExceptionWhenMinimumZeroAndValueZero(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("Precision must be greater than 0 and less than or equal to 16."); + Decimal::fromNumeric(0, -12); + } } From b70a36cf051ab517f6798e1be9aa5c6fe5d90ba7 Mon Sep 17 00:00:00 2001 From: gmitsos Date: Thu, 8 May 2025 13:06:31 +0300 Subject: [PATCH 08/13] cases --- src/Number/NonNegativeDecimal.php | 2 -- tests/Number/DecimalTest.php | 7 +++++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Number/NonNegativeDecimal.php b/src/Number/NonNegativeDecimal.php index 661cdff..a9587d9 100644 --- a/src/Number/NonNegativeDecimal.php +++ b/src/Number/NonNegativeDecimal.php @@ -42,8 +42,6 @@ public static function fromNumeric($value, int $precision = 2, int $rounding = P throw new InvalidArgumentException('Value must be a non-negative decimal number.'); } - //parent::fromNumeric($value, $precision, $rounding); would be cleaner and DRY but redoes is_numeric check - if ($value == 0.0) { $minimumPrecision = 0; } else { diff --git a/tests/Number/DecimalTest.php b/tests/Number/DecimalTest.php index 48509d6..359b86d 100644 --- a/tests/Number/DecimalTest.php +++ b/tests/Number/DecimalTest.php @@ -150,4 +150,11 @@ public function test_FromNumericPrecisionThrowsExceptionWhenMinimumZeroAndValueZ $this->expectExceptionMessage("Precision must be greater than 0 and less than or equal to 16."); Decimal::fromNumeric(0, -12); } + + public function test_MinimumPrecisionIsZeroWhenGivenZeroAsValue(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("Precision must be greater than 0 and less than or equal to 16."); + new Decimal(0, -12); + } } From a55d1d9f7a90a598e8ac17490fb2fd8d8bcac0d6 Mon Sep 17 00:00:00 2001 From: gmitsos Date: Thu, 8 May 2025 13:57:51 +0300 Subject: [PATCH 09/13] update --- src/Number/Decimal.php | 2 +- tests/Number/DecimalTest.php | 21 ++++++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/Number/Decimal.php b/src/Number/Decimal.php index 15183f6..bf27752 100644 --- a/src/Number/Decimal.php +++ b/src/Number/Decimal.php @@ -72,7 +72,7 @@ public static function fromNumeric($value, int $precision = 2, int $rounding = P throw new InvalidArgumentException('Value is not numeric.'); } - if ((float)$value == 0.0) { + if ($value == 0.0) { $minimumPrecision = 0; } else { $minimumPrecision = -(floor(log10(abs((float)$value))) + 1); diff --git a/tests/Number/DecimalTest.php b/tests/Number/DecimalTest.php index 359b86d..d443560 100644 --- a/tests/Number/DecimalTest.php +++ b/tests/Number/DecimalTest.php @@ -78,10 +78,10 @@ public function test_IsNotEqualToSameTypeWithDifferentType(): void public function test_GetMethods(): void { - $decimal = new Decimal(12.345, 2, PHP_ROUND_HALF_UP); + $decimal = new Decimal(12.345, 16, PHP_ROUND_HALF_UP); - $this->assertEquals(12.35, $decimal->getValue()); - $this->assertEquals(2, $decimal->getPrecision()); + $this->assertEquals(12.345, $decimal->getValue()); + $this->assertEquals(16, $decimal->getPrecision()); $this->assertEquals(1, $decimal->getRounding()); } @@ -157,4 +157,19 @@ public function test_MinimumPrecisionIsZeroWhenGivenZeroAsValue(): void $this->expectExceptionMessage("Precision must be greater than 0 and less than or equal to 16."); new Decimal(0, -12); } + + public function test_PrecisionFailsWhenBelowMinimumBasedOnFloor(): void + { + $value = 9.99; + $this->expectException(InvalidArgumentException::class); + new Decimal($value, -1); + } + + public function test_PrecisionPassesWhenJustAboveMinimumBasedOnFloor(): void + { + $value = 9.99; + $decimal = new Decimal($value, 0); + $this->assertEquals(round(9.99, 0), $decimal->getValue()); + } + } From 2efac9743f526ce25005e0434a55490c861fd0b1 Mon Sep 17 00:00:00 2001 From: gmitsos Date: Thu, 8 May 2025 14:30:32 +0300 Subject: [PATCH 10/13] update --- src/Number/Decimal.php | 17 +--------- src/Number/NonNegativeDecimal.php | 15 --------- tests/Number/DecimalTest.php | 53 ++++++++++++++++++++++++++++++- 3 files changed, 53 insertions(+), 32 deletions(-) diff --git a/src/Number/Decimal.php b/src/Number/Decimal.php index bf27752..5b40ecc 100644 --- a/src/Number/Decimal.php +++ b/src/Number/Decimal.php @@ -72,23 +72,8 @@ public static function fromNumeric($value, int $precision = 2, int $rounding = P throw new InvalidArgumentException('Value is not numeric.'); } - if ($value == 0.0) { - $minimumPrecision = 0; - } else { - $minimumPrecision = -(floor(log10(abs((float)$value))) + 1); - } - - if ($precision > 16 || $precision <= $minimumPrecision) { - throw new InvalidArgumentException( - "Precision must be greater than {$minimumPrecision} and less than or equal to 16." - ); - } - - if ($rounding < 1 || $rounding > 4) { - throw new InvalidArgumentException('Rounding must be between 1 and 4.'); - } + $value = (float)$value; - $value = round((float)$value, $precision, $rounding); return new self($value, $precision, $rounding); } } diff --git a/src/Number/NonNegativeDecimal.php b/src/Number/NonNegativeDecimal.php index a9587d9..d14d81a 100644 --- a/src/Number/NonNegativeDecimal.php +++ b/src/Number/NonNegativeDecimal.php @@ -42,21 +42,6 @@ public static function fromNumeric($value, int $precision = 2, int $rounding = P throw new InvalidArgumentException('Value must be a non-negative decimal number.'); } - if ($value == 0.0) { - $minimumPrecision = 0; - } else { - $minimumPrecision = -(floor(log10(abs($value))) + 1); - } - - if ($precision > 16 || $precision <= $minimumPrecision) { - throw new InvalidArgumentException("Precision must be > {$minimumPrecision} and <= 16."); - } - - if ($rounding < 1 || $rounding > 4) { - throw new InvalidArgumentException('Rounding must be between 1 and 4.'); - } - - $value = round($value, $precision, $rounding); return new self($value, $precision, $rounding); } } diff --git a/tests/Number/DecimalTest.php b/tests/Number/DecimalTest.php index d443560..c1a3635 100644 --- a/tests/Number/DecimalTest.php +++ b/tests/Number/DecimalTest.php @@ -45,7 +45,7 @@ public function test_canBeCreatedFromInteger(): void public function test_canNotBeCreatedFromNonNumeric(): void { $this->expectException(InvalidArgumentException::class); - + $this->expectExceptionMessage('Value is not numeric.'); Decimal::fromNumeric('abc'); } @@ -172,4 +172,55 @@ public function test_PrecisionPassesWhenJustAboveMinimumBasedOnFloor(): void $this->assertEquals(round(9.99, 0), $decimal->getValue()); } + public function test_FromNumericMinimumPrecisionIsZeroWhenGivenZeroAsValue(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("Precision must be greater than 0 and less than or equal to 16."); + Decimal::fromNumeric(0, -12); + } + + public function test_FromNumericPrecisionFailsWhenBelowMinimumBasedOnFloor(): void + { + $value = 9.99; + $this->expectException(InvalidArgumentException::class); + Decimal::fromNumeric($value, -1); + } + + public function test_FromNumericPrecisionPassesWhenJustAboveMinimumBasedOnFloor(): void + { + $value = 9.99; + $decimal = Decimal::fromNumeric($value, 0); + $this->assertEquals(round(9.99, 0), $decimal->getValue()); + } + + public function test_FromNumericRoundingValues(): void + { + $decimal = Decimal::fromNumeric(12.345, 2, PHP_ROUND_HALF_UP); + $this->assertEquals(12.35, $decimal->getValue()); + $this->assertEquals(1, $decimal->getRounding()); + + $decimal = Decimal::fromNumeric(12.345, 2, PHP_ROUND_HALF_DOWN); + $this->assertEquals(12.34, $decimal->getValue()); + $this->assertEquals(2, $decimal->getRounding()); + + $decimal = Decimal::fromNumeric(12.345, 2, PHP_ROUND_HALF_EVEN); + $this->assertEquals(12.34, $decimal->getValue()); + $this->assertEquals(3, $decimal->getRounding()); + + $decimal = Decimal::fromNumeric(12.345, 2, PHP_ROUND_HALF_ODD); + $this->assertEquals(12.35, $decimal->getValue()); + $this->assertEquals(4, $decimal->getRounding()); + } + + public function test_FromNumericRoundingThrowsExceptionOnInvalidHigherValue(): void + { + $this->expectException(InvalidArgumentException::class); + Decimal::fromNumeric(12.345, 2, 5); + } + + public function test_FromNumericRoundingThrowsExceptionOnInvalidLowerValue(): void + { + $this->expectException(InvalidArgumentException::class); + Decimal::fromNumeric(12.345, 2, 0); + } } From 90a7863593a579f20aab0a45631c9c75325b3eb3 Mon Sep 17 00:00:00 2001 From: gmitsos Date: Thu, 8 May 2025 14:36:50 +0300 Subject: [PATCH 11/13] update --- tests/Number/NonNegativeDecimalTest.php | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tests/Number/NonNegativeDecimalTest.php b/tests/Number/NonNegativeDecimalTest.php index efa3405..f918544 100644 --- a/tests/Number/NonNegativeDecimalTest.php +++ b/tests/Number/NonNegativeDecimalTest.php @@ -16,9 +16,9 @@ final class NonNegativeDecimalTest extends TestCase { public function test_CanBeCreatedFromPositiveDecimal(): void { - $result = (new NonNegativeDecimal(12.34))->getValue(); + $result = (new NonNegativeDecimal(12.345))->getValue(); - $this->assertEqualsWithDelta(12.34, $result, 0.00001); + $this->assertEqualsWithDelta(12.35, $result, 0.00001); } public function test_CanBeCreatedFromZero(): void @@ -30,9 +30,9 @@ public function test_CanBeCreatedFromZero(): void public function test_canBeCreatedFromNumericString(): void { - $result = NonNegativeDecimal::fromNumeric('12.34')->getValue(); + $result = NonNegativeDecimal::fromNumeric('12.345')->getValue(); - $this->assertEqualsWithDelta(12.34, $result, 0.00001); + $this->assertEqualsWithDelta(12.35, $result, 0.00001); } public function test_canBeCreatedFromFloat(): void @@ -42,9 +42,17 @@ public function test_canBeCreatedFromFloat(): void $this->assertEqualsWithDelta(12.34, $result, 0.00001); } + public function test_canBeCreatedFromZeroFromNumeric(): void + { + $result = NonNegativeDecimal::fromNumeric(0)->getValue(); + + $this->assertEqualsWithDelta(0, $result, 0.00001); + } + public function test_canNotBeCreatedFromNegativeUsingNumericFloat(): void { $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Value must be a non-negative decimal number.'); NonNegativeDecimal::fromNumeric(-12.34); } @@ -52,6 +60,7 @@ public function test_canNotBeCreatedFromNegativeUsingNumericFloat(): void public function test_canNotBeCreatedFromNonNumeric(): void { $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Value is not numeric.'); NonNegativeDecimal::fromNumeric('abc'); } @@ -59,6 +68,7 @@ public function test_canNotBeCreatedFromNonNumeric(): void public function test_CannotBeCreatedFromNegativeFloat(): void { $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Value must be a non-negative decimal number.'); new NonNegativeDecimal(-12.34); } From 93a1a2ff27064aa7a06111b1dbc82a6c1558e156 Mon Sep 17 00:00:00 2001 From: gmitsos Date: Thu, 8 May 2025 14:44:53 +0300 Subject: [PATCH 12/13] update --- src/Number/NonNegativeDecimal.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Number/NonNegativeDecimal.php b/src/Number/NonNegativeDecimal.php index d14d81a..47dcdb1 100644 --- a/src/Number/NonNegativeDecimal.php +++ b/src/Number/NonNegativeDecimal.php @@ -38,10 +38,6 @@ public static function fromNumeric($value, int $precision = 2, int $rounding = P $value = (float)$value; - if ($value < 0) { - throw new InvalidArgumentException('Value must be a non-negative decimal number.'); - } - return new self($value, $precision, $rounding); } } From 6bfc6c4ddf585429d163b689230157918c17b7d6 Mon Sep 17 00:00:00 2001 From: gmitsos Date: Thu, 8 May 2025 15:36:49 +0300 Subject: [PATCH 13/13] add toString --- src/Number/Decimal.php | 5 +++++ src/Number/NonNegativeDecimal.php | 2 +- tests/Number/DecimalTest.php | 6 ++++++ tests/Number/NonNegativeDecimalTest.php | 6 ++++++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/Number/Decimal.php b/src/Number/Decimal.php index 5b40ecc..e295101 100644 --- a/src/Number/Decimal.php +++ b/src/Number/Decimal.php @@ -76,4 +76,9 @@ public static function fromNumeric($value, int $precision = 2, int $rounding = P return new self($value, $precision, $rounding); } + + final public function toString(): string + { + return (string)$this->value; + } } diff --git a/src/Number/NonNegativeDecimal.php b/src/Number/NonNegativeDecimal.php index 47dcdb1..669c45d 100644 --- a/src/Number/NonNegativeDecimal.php +++ b/src/Number/NonNegativeDecimal.php @@ -14,7 +14,7 @@ class NonNegativeDecimal extends Decimal * @param int $rounding (1 : PHP_ROUND_HALF_UP (Default), 2 : PHP_ROUND_HALF_DOWN, * 3 : PHP_ROUND_HALF_EVEN, 4 : PHP_ROUND_HALF_ODD) */ - public function __construct(float $value, int $precision = 2, int $rounding = PHP_ROUND_HALF_UP) + public function __construct($value, int $precision = 2, int $rounding = PHP_ROUND_HALF_UP) { if ($value < 0) { throw new InvalidArgumentException('Value must be a non-negative decimal number.'); diff --git a/tests/Number/DecimalTest.php b/tests/Number/DecimalTest.php index c1a3635..1ef5167 100644 --- a/tests/Number/DecimalTest.php +++ b/tests/Number/DecimalTest.php @@ -223,4 +223,10 @@ public function test_FromNumericRoundingThrowsExceptionOnInvalidLowerValue(): vo $this->expectException(InvalidArgumentException::class); Decimal::fromNumeric(12.345, 2, 0); } + public function test_toString(): void + { + $decimal = new Decimal(123.321, 15, 1); + $this->assertEquals('123.321', $decimal->toString()); + $this->assertSame('123.321', (string) $decimal->getValue()); + } } diff --git a/tests/Number/NonNegativeDecimalTest.php b/tests/Number/NonNegativeDecimalTest.php index f918544..f3d6a44 100644 --- a/tests/Number/NonNegativeDecimalTest.php +++ b/tests/Number/NonNegativeDecimalTest.php @@ -99,4 +99,10 @@ public function test_isNotEqualToDifferentType(): void $this->assertFalse($float1->equals($float2)); $this->assertFalse($float2->equals($float1)); } + + public function test_toString(): void + { + $decimal = new NonNegativeDecimal(123.321, 3); + $this->assertEquals('123.321', $decimal->toString()); + } }