From 620797a47c7b32a093f3c789fc532c7b775f511a Mon Sep 17 00:00:00 2001 From: jon Date: Sun, 7 Jul 2024 22:27:02 +0100 Subject: [PATCH 1/9] wip new math --- src/Helpers/Math/Math.php | 186 +++++++++++++++++++++ src/Helpers/Math/MathContract.php | 50 ++++++ src/Helpers/{Math.php => Math/MathOld.php} | 4 +- tests/ExampleTest.php | 80 ++++++++- 4 files changed, 316 insertions(+), 4 deletions(-) create mode 100644 src/Helpers/Math/Math.php create mode 100644 src/Helpers/Math/MathContract.php rename src/Helpers/{Math.php => Math/MathOld.php} (99%) diff --git a/src/Helpers/Math/Math.php b/src/Helpers/Math/Math.php new file mode 100644 index 0000000..eb8eb6d --- /dev/null +++ b/src/Helpers/Math/Math.php @@ -0,0 +1,186 @@ +defaultScale, $this->roundingMode))->init($number, $scale); + } + + protected function init(float|int|string $number, ?int $scale = null): self + { + $this->number = BigDecimal::of($number); + $this->scale = $scale ?? $this->defaultScale; + return $this; + } + + protected function toBigDecimal(float|int|string $value): BigDecimal + { + // We cant use toScale here because it will round the number, and we want to keep the rounding mode + return BigDecimal::of($value); + } + + public function roundingMode(RoundingMode $mode): self + { + $this->roundingMode = $mode; + return $this; + } + + public function sum(float|int|string $value): self + { + $this->of($this->number->plus($this->toBigDecimal($value))); + return $this; + } + + public function subtract(float|int|string $value): self + { + $this->of($this->number->minus($this->toBigDecimal($value))); + return $this; + } + + public function multiply(float|int|string $value): self + { + $this->of($this->number->multipliedBy($this->toBigDecimal($value))); + return $this; + } + + public function divide(float|int|string $value): self + { + $this->of($this->number->dividedBy($this->toBigDecimal($value), $this->scale, $this->roundingMode)); + return $this; + } + + public function pow(int $exponent): self + { + $this->of($this->number->power($exponent)); + return $this; + } + + public function round(int $precision = 0, ?RoundingMode $roundingMode = null): self + { + $this->of($this->number->toScale($precision, $roundingMode ?? $this->roundingMode)); + return $this; + } + + public function ceil(): self + { + $this->of($this->number->dividedBy(BigDecimal::one(), 0, RoundingMode::CEILING)); + return $this; + } + + public function floor(): self + { + $this->of($this->number->dividedBy(BigDecimal::one(), 0, RoundingMode::FLOOR)); + return $this; + } + + public function absolute(): self + { + $this->number = $this->number->abs(); + return $this; + } + + public function negative(): self + { + if($this->number->isNegative()){ + return $this; + } + $this->number = $this->number->negated(); + return $this; + } + + public function addPercentage(float|int|string $percentage): self + { + $percentageValue = $this + ->number + ->multipliedBy($this->toBigDecimal($percentage)) + ->dividedBy(100, $this->scale, $this->roundingMode); + + $newNumber = $this->number->plus($percentageValue); + return $this->of($newNumber, $this->scale); + } + + public function subtractPercentage(float|int|string $percentage): self + { + $percentageValue = $this + ->number + ->multipliedBy($this->toBigDecimal($percentage)) + ->dividedBy(100, $this->scale, $this->roundingMode); + + $newNumber = $this->number->minus($percentageValue); + return $this->of($newNumber, $this->scale); + } + + public function compare(float|int|string $value): int + { + return $this->number->compareTo($this->toBigDecimal($value)); + } + + public function isLessThan(float|int|string $value): bool + { + return $this->compare($value) < 0; + } + + public function isLessThanOrEqual(float|int|string $value): bool + { + return $this->compare($value) <= 0; + } + + public function isGreaterThan(float|int|string $value): bool + { + return $this->compare($value) > 0; + } + + public function isGreaterThanOrEqual(float|int|string $value): bool + { + return $this->compare($value) >= 0; + } + + public function isEqual(float|int|string $value): bool + { + return $this->compare($value) === 0; + } + + public function isZero(): bool + { + return $this->number->toScale($this->scale, $this->roundingMode)->isZero(); + } + + public function isNotZero(): bool + { + return !$this->isZero(); + } + + public function toInt(): int + { + return $this->number->toInt(); + } + + public function toFloat(): float + { + return $this->number->toScale($this->scale, $this->roundingMode)->toFloat(); + } + + public function toString(): string + { + return $this->number->toScale($this->scale, $this->roundingMode)->__toString(); + } + + public function __toString(): string + { + return $this->toString(); + } +} diff --git a/src/Helpers/Math/MathContract.php b/src/Helpers/Math/MathContract.php new file mode 100644 index 0000000..457c6ac --- /dev/null +++ b/src/Helpers/Math/MathContract.php @@ -0,0 +1,50 @@ +toBeTrue(); +use Brick\Math\RoundingMode; +use Flavorly\LaravelHelpers\Helpers\Math\Math; + +beforeEach(function () { + $this->math = new Math(); +}); + +it('performs basic sum operations', function ($initial, $addend, $expected, $scale = null) { + $math = $scale !== null ? new Math($scale) : $this->math; + $result = $math->of($initial)->sum($addend)->toFloat(); + expect($result)->toBe($expected); +})->with([ + 'integer addition' => [5, 3, 8.00], + 'float addition' => [5.5, 3.3, 8.80], + 'negative number addition' => [5, -3, 2.00], + 'string number addition' => ['5.5', '3.3', 8.80], + 'large number addition' => [1000000, 2000000, 3000000.00], + 'small number addition' => [0.001, 0.002, 0.00], // Rounds to 0.00 with default scale + 'custom scale addition' => [0.001, 0.002, 0.0030, 4], + 'different type addition' => [5, '3.3', 8.30], + 'zero addition' => [5, 0, 5.00], + 'addition resulting in negative' => [5, -10, -5.00], +]); + +it('can chain sum operations', function () { + $result = $this->math->of(5) + ->sum(3) + ->sum(2) + ->sum(1.5) + ->toString(); + + expect($result)->toBe('11.50'); +}); + +it('handles different scales and rounding modes correctly', function () { + // Test with HALF_DOWN (default) + $mathHalfDown = new Math(4, RoundingMode::HALF_DOWN); + $resultHalfDown = $mathHalfDown->of(1.23456)->sum(2.34567)->toFloat(); + expect($resultHalfDown)->toBe(3.5802); + + // Let's see what happens with HALF_UP + $mathHalfUp = new Math(4, RoundingMode::HALF_UP); + $resultHalfUp = $mathHalfUp->of(1.23456)->sum(2.34567)->toFloat(); + expect($resultHalfUp)->toBe(3.5802); + + // Let's check the exact value before rounding + $mathExact = new Math(10, RoundingMode::UNNECESSARY); + $resultExact = $mathExact->of(1.23456)->sum(2.34567)->toString(); + expect($resultExact)->toBe('3.5802300000'); + + // Test rounding at different scales + $mathScale3 = new Math(3, RoundingMode::HALF_DOWN); + $resultScale3 = $mathScale3->of(1.23456)->sum(2.34567)->toFloat(); + expect($resultScale3)->toBe(3.580); + + $mathScale5 = new Math(5, RoundingMode::HALF_DOWN); + $resultScale5 = $mathScale5->of(1.23456)->sum(2.34567)->toFloat(); + expect($resultScale5)->toBe(3.58023); +}); + +it('preserves immutability in sum operations', function () { + $math = new Math(); + $initial = $math->of(5); + $result1 = $initial->sum(3); + $result2 = $initial->sum(2); + + expect($result1->toString())->toBe('8.00'); + expect($result2->toString())->toBe('7.00'); + expect($initial->toString())->toBe('5.00'); +}); + +it('handles very small numbers correctly', function () { + $result = $this->math->of(0.001)->sum(0.002)->toString(); + expect($result)->toBe('0.00'); // With default scale 2 + + $math = new Math(3); + $result = $math->of(0.001)->sum(0.002)->toString(); + expect($result)->toBe('0.003'); // With scale 3 }); From 9735ba4cb964b2245b18c8977eda13e9b0710fac Mon Sep 17 00:00:00 2001 From: jon Date: Sun, 7 Jul 2024 23:43:17 +0100 Subject: [PATCH 2/9] wip new math --- src/Helpers/Math/Math.php | 95 +++++++---- tests/ExampleTest.php | 344 +++++++++++++++++++++++++++++++++++--- 2 files changed, 385 insertions(+), 54 deletions(-) diff --git a/src/Helpers/Math/Math.php b/src/Helpers/Math/Math.php index eb8eb6d..5df29db 100644 --- a/src/Helpers/Math/Math.php +++ b/src/Helpers/Math/Math.php @@ -8,26 +8,20 @@ final class Math { protected BigDecimal $number; - protected int $scale; public function __construct( - protected int $defaultScale = 2, - protected RoundingMode $roundingMode = RoundingMode::HALF_DOWN + protected int $scale = 2, + protected RoundingMode $roundingMode = RoundingMode::DOWN ) {} - public function of(float|int|string $number, ?int $scale = null): self + public static function of(float|int|string $number, ?int $scale = null, ?RoundingMode $roundingMode = null): self { - return (new Math($this->defaultScale, $this->roundingMode))->init($number, $scale); + $instance = new self($scale ?? 2, $roundingMode ?? RoundingMode::DOWN); + $instance->number = BigDecimal::of($number); + return $instance; } - protected function init(float|int|string $number, ?int $scale = null): self - { - $this->number = BigDecimal::of($number); - $this->scale = $scale ?? $this->defaultScale; - return $this; - } - - protected function toBigDecimal(float|int|string $value): BigDecimal + public function toBigDecimal(float|int|string $value): BigDecimal { // We cant use toScale here because it will round the number, and we want to keep the rounding mode return BigDecimal::of($value); @@ -39,52 +33,81 @@ public function roundingMode(RoundingMode $mode): self return $this; } - public function sum(float|int|string $value): self + public function roundDown(): self { - $this->of($this->number->plus($this->toBigDecimal($value))); + $this->roundingMode = RoundingMode::DOWN; return $this; } - public function subtract(float|int|string $value): self + public function roundUp(): self { - $this->of($this->number->minus($this->toBigDecimal($value))); + $this->roundingMode = RoundingMode::UP; return $this; } - public function multiply(float|int|string $value): self + public function scale(int $scale): self { - $this->of($this->number->multipliedBy($this->toBigDecimal($value))); + $this->scale = $scale; return $this; } + public function sum(float|int|string $value): self + { + $new = new self($this->scale, $this->roundingMode); + $new->number = $this->number->plus(BigDecimal::of($value)); + return $new; + } + + public function subtract(float|int|string $value): self + { + $new = new self($this->scale, $this->roundingMode); + $new->number = $this->number->minus($this->toBigDecimal($value)); + return $new; + } + + public function multiply(float|int|string $value): self + { + $new = new self($this->scale, $this->roundingMode); + $new->number = $this->number->multipliedBy($this->toBigDecimal($value)); + return $new; + } + public function divide(float|int|string $value): self { - $this->of($this->number->dividedBy($this->toBigDecimal($value), $this->scale, $this->roundingMode)); - return $this; + $new = new self($this->scale, $this->roundingMode); + $new->number = $this->number->dividedBy($this->toBigDecimal($value), $this->scale, $this->roundingMode); + return $new; } public function pow(int $exponent): self { - $this->of($this->number->power($exponent)); - return $this; + $new = new self($this->scale, $this->roundingMode); + $new->number = $this->number->power($exponent); + return $new; } public function round(int $precision = 0, ?RoundingMode $roundingMode = null): self { - $this->of($this->number->toScale($precision, $roundingMode ?? $this->roundingMode)); - return $this; + $new = new self($this->scale, $this->roundingMode); + $new->number = $this->number->toScale($precision, $roundingMode ?? $this->roundingMode); + + return $new; } public function ceil(): self { - $this->of($this->number->dividedBy(BigDecimal::one(), 0, RoundingMode::CEILING)); - return $this; + $new = new self($this->scale, $this->roundingMode); + $new->number = $this->number->dividedBy(BigDecimal::one(), 0, RoundingMode::CEILING); + + return $new; } public function floor(): self { - $this->of($this->number->dividedBy(BigDecimal::one(), 0, RoundingMode::FLOOR)); - return $this; + $new = new self($this->scale, $this->roundingMode); + $new->number = $this->number->dividedBy(BigDecimal::one(), 0, RoundingMode::FLOOR); + + return $new; } public function absolute(): self @@ -110,7 +133,11 @@ public function addPercentage(float|int|string $percentage): self ->dividedBy(100, $this->scale, $this->roundingMode); $newNumber = $this->number->plus($percentageValue); - return $this->of($newNumber, $this->scale); + + $new = new self($this->scale, $this->roundingMode); + $new->number = $newNumber; + + return $new; } public function subtractPercentage(float|int|string $percentage): self @@ -121,7 +148,11 @@ public function subtractPercentage(float|int|string $percentage): self ->dividedBy(100, $this->scale, $this->roundingMode); $newNumber = $this->number->minus($percentageValue); - return $this->of($newNumber, $this->scale); + + $new = new self($this->scale, $this->roundingMode); + $new->number = $newNumber; + + return $new; } public function compare(float|int|string $value): int @@ -166,7 +197,7 @@ public function isNotZero(): bool public function toInt(): int { - return $this->number->toInt(); + return $this->number->toScale(0, $this->roundingMode)->toInt(); } public function toFloat(): float diff --git a/tests/ExampleTest.php b/tests/ExampleTest.php index fd8ff58..82f4144 100644 --- a/tests/ExampleTest.php +++ b/tests/ExampleTest.php @@ -1,5 +1,7 @@ math; - $result = $math->of($initial)->sum($addend)->toFloat(); + $math = Math::of($initial, $scale); + $result = $math->sum($addend)->toFloat(); expect($result)->toBe($expected); })->with([ 'integer addition' => [5, 3, 8.00], @@ -36,33 +38,64 @@ it('handles different scales and rounding modes correctly', function () { // Test with HALF_DOWN (default) - $mathHalfDown = new Math(4, RoundingMode::HALF_DOWN); - $resultHalfDown = $mathHalfDown->of(1.23456)->sum(2.34567)->toFloat(); + $resultHalfDown = Math::of(1.23456) + ->scale(4) + ->roundDown() + ->sum(2.34567) + ->toFloat(); expect($resultHalfDown)->toBe(3.5802); - // Let's see what happens with HALF_UP - $mathHalfUp = new Math(4, RoundingMode::HALF_UP); - $resultHalfUp = $mathHalfUp->of(1.23456)->sum(2.34567)->toFloat(); - expect($resultHalfUp)->toBe(3.5802); + // Test with HALF_UP + $resultHalfUp = Math::of(1.23456) + ->scale(4) + ->roundUp() + ->sum(2.34567) + ->toFloat(); + expect($resultHalfUp)->toBe(3.5803); - // Let's check the exact value before rounding - $mathExact = new Math(10, RoundingMode::UNNECESSARY); - $resultExact = $mathExact->of(1.23456)->sum(2.34567)->toString(); + // Check the exact value before rounding + $resultExact = Math::of(1.23456) + ->scale(10) + ->sum(2.34567) + ->toString(); expect($resultExact)->toBe('3.5802300000'); // Test rounding at different scales - $mathScale3 = new Math(3, RoundingMode::HALF_DOWN); - $resultScale3 = $mathScale3->of(1.23456)->sum(2.34567)->toFloat(); + $resultScale3 = Math::of(1.23456) + ->scale(3) + ->roundDown() + ->sum(2.34567) + ->toFloat(); expect($resultScale3)->toBe(3.580); - $mathScale5 = new Math(5, RoundingMode::HALF_DOWN); - $resultScale5 = $mathScale5->of(1.23456)->sum(2.34567)->toFloat(); + $resultScale5 = Math::of(1.23456) + ->scale(5) + ->roundDown() + ->sum(2.34567) + ->toFloat(); expect($resultScale5)->toBe(3.58023); }); +it('handles edge cases correctly', function () { + // Testing numbers that are exactly at the rounding point + $resultEdge = Math::of(1.23455) + ->scale(4) + ->roundUp() + ->sum(2.34565) + ->toFloat(); + expect($resultEdge)->toBe(3.5802); + + // Testing negative numbers + $resultNegative = Math::of(-1.23456) + ->scale(4) + ->roundDown() + ->sum(-2.34567) + ->toFloat(); + expect($resultNegative)->toBe(-3.5802); +}); + it('preserves immutability in sum operations', function () { - $math = new Math(); - $initial = $math->of(5); + $initial = Math::of(5); $result1 = $initial->sum(3); $result2 = $initial->sum(2); @@ -72,10 +105,277 @@ }); it('handles very small numbers correctly', function () { - $result = $this->math->of(0.001)->sum(0.002)->toString(); - expect($result)->toBe('0.00'); // With default scale 2 + // Default scale (2) + $result1 = Math::of(0.001)->sum(0.002)->toString(); + expect($result1)->toBe('0.00'); + + // Scale 3 + $result2 = Math::of(0.001) + ->scale(3) + ->sum(0.002) + ->toString(); + expect($result2)->toBe('0.003'); + + // Scale 4 + $result3 = Math::of(0.0001) + ->scale(4) + ->sum(0.0002) + ->toString(); + expect($result3)->toBe('0.0003'); + + // Scale 5 with subtraction + $result4 = Math::of(0.00001) + ->scale(5) + ->subtract(0.00002) + ->toString(); + expect($result4)->toBe('-0.00001'); + + // Scale 6 with multiplication + $result5 = Math::of(0.000001) + ->scale(6) + ->multiply(1000) + ->toString(); + expect($result5)->toBe('0.001000'); + + // Scale 7 with division + $result6 = Math::of(0.0000001) + ->scale(7) + ->divide(0.1) + ->toString(); + expect($result6)->toBe('0.0000010'); + + // Handling very small numbers with rounding + $result7 = Math::of(0.0000001) + ->scale(6) + ->roundUp() + ->toString(); + expect($result7)->toBe('0.000001'); + + $result8 = Math::of(0.0000001) + ->scale(6) + ->roundDown() + ->toString(); + expect($result8)->toBe('0.000000'); + + $result9 = Math::of(0.0000005) + ->scale(6) + ->roundUp() + ->toString(); + expect($result9)->toBe('0.000001'); + + $result10 = Math::of(0.0000005) + ->scale(6) + ->roundDown() + ->toString(); + expect($result10)->toBe('0.000000'); + + // Testing the default HALF_DOWN behavior + $result11 = Math::of(0.0000005) + ->scale(6) + ->toString(); + expect($result11)->toBe('0.000000'); + + $result12 = Math::of(0.0000006) + ->scale(6) + ->toString(); + expect($result12)->toBe('0.000000'); +}); + +it('performs basic arithmetic operations correctly', function () { + // Subtraction + expect(Math::of(10)->subtract(3)->toFloat())->toBe(7.00); + expect(Math::of(5.5)->subtract(2.2)->toFloat())->toBe(3.30); + expect(Math::of(1)->subtract(1.1)->toFloat())->toBe(-0.10); + + // Multiplication + expect(Math::of(5)->multiply(3)->toFloat())->toBe(15.00); + expect(Math::of(2.5)->multiply(2.5)->toFloat())->toBe(6.25); + expect(Math::of(100)->multiply(0.1)->toFloat())->toBe(10.00); + + // Division + // Division with different scales + expect(Math::of(1)->scale(3)->divide(3)->toFloat())->toBe(0.333); + expect(Math::of(1)->scale(4)->divide(3)->toFloat())->toBe(0.3333); + expect(Math::of(1)->scale(5)->divide(3)->toFloat())->toBe(0.33333); + + // Division with rounding + expect(Math::of(1)->scale(2)->divide(3)->toFloat())->toBe(0.33); + expect(Math::of(1)->scale(2)->roundUp()->divide(3)->toFloat())->toBe(0.34); + + // More complex division scenarios + expect(Math::of(10)->scale(4)->divide(3)->toFloat())->toBe(3.3333); + expect(Math::of(2)->scale(3)->divide(3)->toFloat())->toBe(0.666); + + // Division resulting in repeating decimals + expect(Math::of(1)->scale(6)->divide(7)->toFloat())->toBe(0.142857); + expect(Math::of(1)->scale(8)->divide(6)->toFloat())->toBe(0.16666666); + + // Division by very small numbers + expect(Math::of(1)->scale(4)->divide(0.0001)->toFloat())->toBe(10000.0000); + + // Division of very small numbers + expect(Math::of(0.0001)->scale(8)->divide(0.0001)->toFloat())->toBe(1.00000000); + + // Power + expect(Math::of(2)->pow(3)->toFloat())->toBe(8.00); + expect(Math::of(3)->pow(2)->toFloat())->toBe(9.00); + expect(Math::of(10)->pow(0)->toFloat())->toBe(1.00); + + // Combining operations + $result = Math::of(10) + ->subtract(2) // 8 + ->multiply(3) // 24 + ->divide(4) // 6 + ->pow(2) // 36 + ->toFloat(); + + // Due to potential floating-point precision issues, let's use a delta + expect($result)->toBe(36.00, 2); + + // Alternatively, we can use toString() for exact comparison + expect(Math::of(10) + ->subtract(2) + ->multiply(3) + ->divide(4) + ->pow(2) + ->toString() + )->toBe('36.00'); + + + // Operations with negative numbers + expect(Math::of(-5)->subtract(3)->toFloat())->toBe(-8.00); + expect(Math::of(-5)->multiply(-2)->toFloat())->toBe(10.00); + expect(Math::of(-10)->divide(2)->toFloat())->toBe(-5.00); + expect(Math::of(-2)->pow(3)->toFloat())->toBe(-8.00); + + // Operations with very small numbers + expect(Math::of(0.1)->subtract(0.09)->scale(3)->toFloat())->toBe(0.010); + expect(Math::of(0.1)->multiply(0.1)->scale(3)->toFloat())->toBe(0.010); + expect(Math::of(0.01)->scale(4)->divide(10)->toFloat())->toBe(0.0010); + + // Operations with very large numbers + expect(Math::of(1000000)->multiply(1000000)->toFloat())->toBe(1000000000000.00); + expect(Math::of(1000000000000)->divide(1000000)->toFloat())->toBe(1000000.00); +}); + +it('compares numbers correctly', function () { + // isLessThan + expect(Math::of(5)->isLessThan(10))->toBeTrue(); + expect(Math::of(10)->isLessThan(5))->toBeFalse(); + expect(Math::of(5)->isLessThan(5))->toBeFalse(); + + // isLessThanOrEqual + expect(Math::of(5)->isLessThanOrEqual(10))->toBeTrue(); + expect(Math::of(5)->isLessThanOrEqual(5))->toBeTrue(); + expect(Math::of(10)->isLessThanOrEqual(5))->toBeFalse(); + + // isGreaterThan + expect(Math::of(10)->isGreaterThan(5))->toBeTrue(); + expect(Math::of(5)->isGreaterThan(10))->toBeFalse(); + expect(Math::of(5)->isGreaterThan(5))->toBeFalse(); + + // isGreaterThanOrEqual + expect(Math::of(10)->isGreaterThanOrEqual(5))->toBeTrue(); + expect(Math::of(5)->isGreaterThanOrEqual(5))->toBeTrue(); + expect(Math::of(5)->isGreaterThanOrEqual(10))->toBeFalse(); + + // isEqual + expect(Math::of(5)->isEqual(5))->toBeTrue(); + expect(Math::of(5)->isEqual(10))->toBeFalse(); + + // Comparing with different types + expect(Math::of(5)->isEqual('5'))->toBeTrue(); + expect(Math::of(5.0)->isEqual(5))->toBeTrue(); + + // Comparing with small differences + expect(Math::of(0.1 + 0.2)->isEqual(0.3))->toBeTrue(); + + // Comparing with different scales + expect(Math::of(1)->scale(2)->isEqual(1.00))->toBeTrue(); + expect(Math::of(1)->scale(2)->isEqual(1.001))->toBeFalse(); + + // Comparing negative numbers + expect(Math::of(-5)->isLessThan(-3))->toBeTrue(); + expect(Math::of(-3)->isGreaterThan(-5))->toBeTrue(); +}); + +it('performs utility operations correctly', function () { + expect(Math::of(-5)->absolute()->toFloat())->toBe(5.0); + expect(Math::of(3)->negative()->toFloat())->toBe(-3.0); + expect(Math::of(3.7)->ceil()->toFloat())->toBe(4.0); + expect(Math::of(3.2)->floor()->toFloat())->toBe(3.0); + expect(Math::of('3.14159')->round(2)->toString())->toBe('3.14'); +}); + +it('handles percentage operations correctly', function () { + $math = Math::of(100); + + // Testing addition of percentage + $newMath = $math->addPercentage(50); // Adding 50% + expect($newMath->toFloat())->toBe(150.0); + + // Testing subtraction of percentage + $newMath = $math->subtractPercentage(50); // Subtracting 50% + expect($newMath->toFloat())->toBe(50.0); +}); + +it('can chain multiple different operations', function () { + $result = Math::of(100) + ->addPercentage(10) // 110 + ->multiply(2) // 220 + ->subtract(20) // 200 + ->divide(2) // 100 + ->sum(50) // 150 + ->roundUp() // Should round up here if needed + ->toFloat(); + + expect($result)->toBe(150.0); +}); + +it('handles errors correctly', function () { + $this->expectException(DivisionByZeroException::class); + + Math::of(100)->divide(0)->toFloat(); + + $this->expectException(TypeError::class); + + Math::of('invalid')->sum('oops'); + + $this->expectException(NegativeNumberException::class); + + Math::of(-100)->absolute()->negative()->toInt(); +}); + +it('maintains precision with very large numbers', function () { + $largeNumber = Math::of('999999999999999999999999999999')->sum('1')->scale(0); + expect($largeNumber->toString())->toBe('1000000000000000000000000000000'); + + $multiplied = $largeNumber->multiply('100000000000000000000')->scale(0); + expect($multiplied->toString())->toBe('100000000000000000000000000000000000000000000000000'); +}); + +it('converts to different formats correctly', function () { + $math = Math::of(1234.5678); + + expect($math->toInt())->toBe(1234); + expect($math->toFloat())->toBe(1234.56); + expect($math->toString())->toBe('1234.56'); +}); + +it('allows changing scale and rounding mode mid-calculation', function () { + $math = Math::of(100.123456) + ->scale(5) // Now scale is set first + ->multiply(2) + ->roundingMode(RoundingMode::UP) // Rounding after multiplication + ->sum(0.00002); // Tiny sum here + + expect($math->toString())->toBe('200.24694'); // Adjusted expected outcome +}); + +it('handles operations with mixed scales correctly', function () { + $num1 = Math::of('123.456', 3); // Scale 3 + $num2 = Math::of('0.7891', 4); // Scale 4 - $math = new Math(3); - $result = $math->of(0.001)->sum(0.002)->toString(); - expect($result)->toBe('0.003'); // With scale 3 + $result = $num1->sum($num2); + expect($result->toString())->toBe('124.245'); // Expecting concatenated scale 4 }); From 80a46c27e1a9ca00a9a9c20fd00daec6a5528c6d Mon Sep 17 00:00:00 2001 From: jon Date: Mon, 8 Jul 2024 00:34:16 +0100 Subject: [PATCH 3/9] cleanup --- src/Helpers/Math/Math.php | 191 +++++++++++++++++++++++++++----------- tests/ExampleTest.php | 8 +- 2 files changed, 142 insertions(+), 57 deletions(-) diff --git a/src/Helpers/Math/Math.php b/src/Helpers/Math/Math.php index 5df29db..80b421b 100644 --- a/src/Helpers/Math/Math.php +++ b/src/Helpers/Math/Math.php @@ -3,27 +3,70 @@ namespace Flavorly\LaravelHelpers\Helpers\Math; use Brick\Math\BigDecimal; +use Brick\Math\Exception\DivisionByZeroException; +use Brick\Math\Exception\NumberFormatException; use Brick\Math\RoundingMode; final class Math { - protected BigDecimal $number; - public function __construct( + /** + * This is the actual number that we want to work with + * + * @var BigDecimal $number + */ + protected float|string|int|BigDecimal $number, + /** + * This is the actual scale that we want to use to sums, etc, etc + * + * @var int $scale + */ protected int $scale = 2, + /** + * This is the scale that we want to use to store the numbers + * So 100 with a scale of 2 will store 10000, we add the digits to the right to ensure we have enough space & precision + * + * @var int $storageScale + */ + protected int $storageScale = 10, + /** + * How we want to round the numbers + * + * @var RoundingMode $roundingMode + */ protected RoundingMode $roundingMode = RoundingMode::DOWN - ) {} + ) { + if(!$number instanceof BigDecimal){ + $this->number = BigDecimal::of($number); + } + } - public static function of(float|int|string $number, ?int $scale = null, ?RoundingMode $roundingMode = null): self + /** + * A static factory method to create a new instance of the class. + * + * @param float|int|string $number + * @param int|null $scale + * @param int|null $storageScale + * @param RoundingMode|null $roundingMode + * @return Math + */ + public static function of( + float|int|string $number, + ?int $scale = null, + ?int $storageScale = null, + ?RoundingMode $roundingMode = null + ): self { - $instance = new self($scale ?? 2, $roundingMode ?? RoundingMode::DOWN); - $instance->number = BigDecimal::of($number); - return $instance; + return new self( + $number, + $scale ?? 2, + $storageScale ?? 10, + $roundingMode ?? RoundingMode::DOWN + ); } public function toBigDecimal(float|int|string $value): BigDecimal { - // We cant use toScale here because it will round the number, and we want to keep the rounding mode return BigDecimal::of($value); } @@ -51,69 +94,101 @@ public function scale(int $scale): self return $this; } + public function storageScale(int $storageScale): self + { + $this->storageScale = $storageScale; + + return $this; + } + public function sum(float|int|string $value): self { - $new = new self($this->scale, $this->roundingMode); - $new->number = $this->number->plus(BigDecimal::of($value)); - return $new; + return self::of( + $this->number->plus($this->toBigDecimal($value)), + $this->scale, + $this->storageScale, + $this->roundingMode + ); } public function subtract(float|int|string $value): self { - $new = new self($this->scale, $this->roundingMode); - $new->number = $this->number->minus($this->toBigDecimal($value)); - return $new; + return self::of( + $this->number->minus($this->toBigDecimal($value)), + $this->scale, + $this->storageScale, + $this->roundingMode + ); } public function multiply(float|int|string $value): self { - $new = new self($this->scale, $this->roundingMode); - $new->number = $this->number->multipliedBy($this->toBigDecimal($value)); - return $new; + return self::of( + $this->number->multipliedBy($this->toBigDecimal($value)), + $this->scale, + $this->storageScale, + $this->roundingMode + ); } public function divide(float|int|string $value): self { - $new = new self($this->scale, $this->roundingMode); - $new->number = $this->number->dividedBy($this->toBigDecimal($value), $this->scale, $this->roundingMode); - return $new; + return self::of( + $this->number->dividedBy($this->toBigDecimal($value), $this->scale, $this->roundingMode), + $this->scale, + $this->storageScale, + $this->roundingMode + ); } public function pow(int $exponent): self { - $new = new self($this->scale, $this->roundingMode); - $new->number = $this->number->power($exponent); - return $new; + return self::of( + $this->number->power($exponent), + $this->scale, + $this->storageScale, + $this->roundingMode + ); } public function round(int $precision = 0, ?RoundingMode $roundingMode = null): self { - $new = new self($this->scale, $this->roundingMode); - $new->number = $this->number->toScale($precision, $roundingMode ?? $this->roundingMode); - - return $new; + return self::of( + $this->number->toScale($precision, $roundingMode ?? $this->roundingMode), + $this->scale, + $this->storageScale, + $this->roundingMode + ); } public function ceil(): self { - $new = new self($this->scale, $this->roundingMode); - $new->number = $this->number->dividedBy(BigDecimal::one(), 0, RoundingMode::CEILING); - - return $new; + return self::of( + $this->number->dividedBy(BigDecimal::one(), 0, RoundingMode::CEILING), + $this->scale, + $this->storageScale, + $this->roundingMode + ); } public function floor(): self { - $new = new self($this->scale, $this->roundingMode); - $new->number = $this->number->dividedBy(BigDecimal::one(), 0, RoundingMode::FLOOR); - - return $new; + return self::of( + $this->number->dividedBy(BigDecimal::one(), 0, RoundingMode::FLOOR), + $this->scale, + $this->storageScale, + $this->roundingMode + ); } public function absolute(): self { - $this->number = $this->number->abs(); - return $this; + return self::of( + $this->number->abs(), + $this->scale, + $this->storageScale, + $this->roundingMode + ); } public function negative(): self @@ -121,8 +196,13 @@ public function negative(): self if($this->number->isNegative()){ return $this; } - $this->number = $this->number->negated(); - return $this; + + return self::of( + $this->number->negated(), + $this->scale, + $this->storageScale, + $this->roundingMode + ); } public function addPercentage(float|int|string $percentage): self @@ -132,12 +212,12 @@ public function addPercentage(float|int|string $percentage): self ->multipliedBy($this->toBigDecimal($percentage)) ->dividedBy(100, $this->scale, $this->roundingMode); - $newNumber = $this->number->plus($percentageValue); - - $new = new self($this->scale, $this->roundingMode); - $new->number = $newNumber; - - return $new; + return self::of( + $this->number->plus($percentageValue), + $this->scale, + $this->storageScale, + $this->roundingMode + ); } public function subtractPercentage(float|int|string $percentage): self @@ -147,17 +227,24 @@ public function subtractPercentage(float|int|string $percentage): self ->multipliedBy($this->toBigDecimal($percentage)) ->dividedBy(100, $this->scale, $this->roundingMode); - $newNumber = $this->number->minus($percentageValue); - - $new = new self($this->scale, $this->roundingMode); - $new->number = $newNumber; - - return $new; + return self::of( + $this->number->minus($percentageValue), + $this->scale, + $this->storageScale, + $this->roundingMode + ); } public function compare(float|int|string $value): int { - return $this->number->compareTo($this->toBigDecimal($value)); + // TODO: Check this. + $other = self::of( + $value, + $this->scale, + $this->storageScale, + $this->roundingMode + ); + return $this->number->compareTo($other->number->toBigDecimal()); } public function isLessThan(float|int|string $value): bool diff --git a/tests/ExampleTest.php b/tests/ExampleTest.php index 82f4144..9fbc399 100644 --- a/tests/ExampleTest.php +++ b/tests/ExampleTest.php @@ -5,10 +5,6 @@ use Brick\Math\RoundingMode; use Flavorly\LaravelHelpers\Helpers\Math\Math; -beforeEach(function () { - $this->math = new Math(); -}); - it('performs basic sum operations', function ($initial, $addend, $expected, $scale = null) { $math = Math::of($initial, $scale); $result = $math->sum($addend)->toFloat(); @@ -27,7 +23,7 @@ ]); it('can chain sum operations', function () { - $result = $this->math->of(5) + $result = Math::of(5) ->sum(3) ->sum(2) ->sum(1.5) @@ -297,6 +293,8 @@ // Comparing negative numbers expect(Math::of(-5)->isLessThan(-3))->toBeTrue(); expect(Math::of(-3)->isGreaterThan(-5))->toBeTrue(); + + expect(Math::of(10.0313131)->scale(10)->isLessThan(9.0313131))->toBeFalse(); }); it('performs utility operations correctly', function () { From 15b973e7e3fedf0517abe1489f8bf27de06257f6 Mon Sep 17 00:00:00 2001 From: jon Date: Mon, 8 Jul 2024 01:13:30 +0100 Subject: [PATCH 4/9] Docblocks & Comments Test storage & retrieval --- src/Helpers/Math/Math.php | 317 +++++++++++++++++++++++++++++++++++--- tests/ExampleTest.php | 9 ++ 2 files changed, 306 insertions(+), 20 deletions(-) diff --git a/src/Helpers/Math/Math.php b/src/Helpers/Math/Math.php index 80b421b..6794394 100644 --- a/src/Helpers/Math/Math.php +++ b/src/Helpers/Math/Math.php @@ -4,7 +4,9 @@ use Brick\Math\BigDecimal; use Brick\Math\Exception\DivisionByZeroException; +use Brick\Math\Exception\MathException; use Brick\Math\Exception\NumberFormatException; +use Brick\Math\Exception\RoundingNecessaryException; use Brick\Math\RoundingMode; final class Math @@ -55,9 +57,9 @@ public static function of( ?int $scale = null, ?int $storageScale = null, ?RoundingMode $roundingMode = null - ): self + ): Math { - return new self( + return new Math( $number, $scale ?? 2, $storageScale ?? 10, @@ -65,43 +67,87 @@ public static function of( ); } + /** + * Converts a float, int or string to a BigDecimal + * + * @param float|int|string $value + * @return BigDecimal + * @throws DivisionByZeroException + * @throws NumberFormatException + */ public function toBigDecimal(float|int|string $value): BigDecimal { return BigDecimal::of($value); } - public function roundingMode(RoundingMode $mode): self + /** + * Sets the rounding mode up or down + * @param RoundingMode $mode + * @return $this + */ + public function roundingMode(RoundingMode $mode): Math { $this->roundingMode = $mode; return $this; } - public function roundDown(): self + /** + * Sets the rounding mode to down + * + * @return $this + */ + public function roundDown(): Math { $this->roundingMode = RoundingMode::DOWN; return $this; } - public function roundUp(): self + /** + * Sets the rounding mode to up + * + * @return $this + */ + public function roundUp(): Math { $this->roundingMode = RoundingMode::UP; return $this; } - public function scale(int $scale): self + /** + * Sets the scale of the number + * + * @param int $scale + * @return $this + */ + public function scale(int $scale): Math { $this->scale = $scale; return $this; } - public function storageScale(int $storageScale): self + /** + * Sets the storage scale of the number + * + * @param int $storageScale + * @return $this + */ + public function storageScale(int $storageScale): Math { $this->storageScale = $storageScale; return $this; } - public function sum(float|int|string $value): self + /** + * Adds a value to the current number + * + * @param float|int|string $value + * @return self + * @throws DivisionByZeroException + * @throws NumberFormatException + * @throws MathException + */ + public function sum(float|int|string $value): Math { return self::of( $this->number->plus($this->toBigDecimal($value)), @@ -111,7 +157,16 @@ public function sum(float|int|string $value): self ); } - public function subtract(float|int|string $value): self + /** + * Subtracts a value from the current number + * + * @param float|int|string $value + * @return self + * @throws DivisionByZeroException + * @throws NumberFormatException + * @throws MathException + */ + public function subtract(float|int|string $value): Math { return self::of( $this->number->minus($this->toBigDecimal($value)), @@ -121,7 +176,16 @@ public function subtract(float|int|string $value): self ); } - public function multiply(float|int|string $value): self + /** + * Multiplies the current number by a value + * + * @param float|int|string $value + * @return self + * @throws DivisionByZeroException + * @throws NumberFormatException + * @throws MathException + */ + public function multiply(float|int|string $value): Math { return self::of( $this->number->multipliedBy($this->toBigDecimal($value)), @@ -131,7 +195,16 @@ public function multiply(float|int|string $value): self ); } - public function divide(float|int|string $value): self + /** + * Divides the current number by a value + * + * @param float|int|string $value + * @return self + * @throws DivisionByZeroException + * @throws MathException + * @throws NumberFormatException + */ + public function divide(float|int|string $value): Math { return self::of( $this->number->dividedBy($this->toBigDecimal($value), $this->scale, $this->roundingMode), @@ -141,7 +214,13 @@ public function divide(float|int|string $value): self ); } - public function pow(int $exponent): self + /** + * Raises the current number to the power of an exponent + * + * @param int $exponent + * @return self + */ + public function pow(int $exponent): Math { return self::of( $this->number->power($exponent), @@ -151,7 +230,15 @@ public function pow(int $exponent): self ); } - public function round(int $precision = 0, ?RoundingMode $roundingMode = null): self + /** + * Rounds the current number to the given precision + * + * @param int $precision + * @param RoundingMode|null $roundingMode + * @return self + * @throws RoundingNecessaryException + */ + public function round(int $precision = 0, ?RoundingMode $roundingMode = null): Math { return self::of( $this->number->toScale($precision, $roundingMode ?? $this->roundingMode), @@ -161,7 +248,13 @@ public function round(int $precision = 0, ?RoundingMode $roundingMode = null): s ); } - public function ceil(): self + /** + * Rounds the current number up to the nearest + * + * @return self + * @throws MathException + */ + public function ceil(): Math { return self::of( $this->number->dividedBy(BigDecimal::one(), 0, RoundingMode::CEILING), @@ -171,7 +264,13 @@ public function ceil(): self ); } - public function floor(): self + /** + * Rounds the current number down to the nearest + * + * @return self + * @throws MathException + */ + public function floor(): Math { return self::of( $this->number->dividedBy(BigDecimal::one(), 0, RoundingMode::FLOOR), @@ -181,7 +280,12 @@ public function floor(): self ); } - public function absolute(): self + /** + * Returns the absolute value of the current number + * + * @return self + */ + public function absolute(): Math { return self::of( $this->number->abs(), @@ -191,7 +295,12 @@ public function absolute(): self ); } - public function negative(): self + /** + * Returns the negative value of the current number + * + * @return $this|self + */ + public function negative(): Math { if($this->number->isNegative()){ return $this; @@ -205,7 +314,16 @@ public function negative(): self ); } - public function addPercentage(float|int|string $percentage): self + /** + * Adds a percentage to the current number + * + * @param float|int|string $percentage + * @return Math + * @throws DivisionByZeroException + * @throws MathException + * @throws NumberFormatException + */ + public function addPercentage(float|int|string $percentage): Math { $percentageValue = $this ->number @@ -220,7 +338,16 @@ public function addPercentage(float|int|string $percentage): self ); } - public function subtractPercentage(float|int|string $percentage): self + /** + * Subtracts a percentage from the current number + * + * @param float|int|string $percentage + * @return Math + * @throws DivisionByZeroException + * @throws MathException + * @throws NumberFormatException + */ + public function subtractPercentage(float|int|string $percentage): Math { $percentageValue = $this ->number @@ -235,9 +362,16 @@ public function subtractPercentage(float|int|string $percentage): self ); } + /** + * Compares the current number to another value + * + * @param float|int|string $value + * @return int + * @throws MathException + * @throws RoundingNecessaryException + */ public function compare(float|int|string $value): int { - // TODO: Check this. $other = self::of( $value, $this->scale, @@ -247,56 +381,199 @@ public function compare(float|int|string $value): int return $this->number->compareTo($other->number->toBigDecimal()); } + /** + * Checks if the current number is less than another value + * + * @param float|int|string $value + * @return bool + * @throws MathException + * @throws RoundingNecessaryException + */ public function isLessThan(float|int|string $value): bool { return $this->compare($value) < 0; } + /** + * Checks if the current number is less than or equal to another value + * + * @param float|int|string $value + * @return bool + * @throws MathException + * @throws RoundingNecessaryException + */ public function isLessThanOrEqual(float|int|string $value): bool { return $this->compare($value) <= 0; } + /** + * Checks if the current number is greater than another value + * + * @param float|int|string $value + * @return bool + * @throws MathException + * @throws RoundingNecessaryException + */ public function isGreaterThan(float|int|string $value): bool { return $this->compare($value) > 0; } + /** + * Checks if the current number is greater than or equal to another value + * + * @param float|int|string $value + * @return bool + * @throws MathException + * @throws RoundingNecessaryException + */ public function isGreaterThanOrEqual(float|int|string $value): bool { return $this->compare($value) >= 0; } + /** + * Checks if the current number is equal to another value + * + * @param float|int|string $value + * @return bool + * @throws MathException + * @throws RoundingNecessaryException + */ public function isEqual(float|int|string $value): bool { return $this->compare($value) === 0; } + /** + * Checks if the current number is zero + * + * @return bool + * @throws MathException + * @throws RoundingNecessaryException + */ public function isZero(): bool { return $this->number->toScale($this->scale, $this->roundingMode)->isZero(); } + /** + * Checks if the current number is not zero + * + * @return bool + * @throws MathException + * @throws RoundingNecessaryException + */ public function isNotZero(): bool { return !$this->isZero(); } + /** + * Ensures the scale of the current number + * + * @return Math + * @throws RoundingNecessaryException + */ + public function ensureScale(): Math + { + return self::of( + $this->number->toScale($this->scale, $this->roundingMode), + $this->scale, + $this->storageScale, + $this->roundingMode, + ); + } + + /** + * Returns the current number as an integer to save on storage + * + * @return int + * @throws MathException + * @throws RoundingNecessaryException + */ + public function toStorageScale(): int + { + $decimalPlaces = BigDecimal::of(10)->power($this->storageScale); + $numberRounded = $this->ensureScale(); + + return $numberRounded + ->multiply($decimalPlaces) + ->ensureScale() + ->toInt(); + } + + /** + * Returns the current number from the storage as a Math object + * + * @return Math + * @throws DivisionByZeroException + * @throws MathException + * @throws NumberFormatException + */ + function fromStorage(): Math + { + $decimalPlaces = BigDecimal::of(10)->power($this->storageScale); + + return self::of( + $this->number->dividedBy($decimalPlaces, $this->scale, $this->roundingMode), + $this->scale, + $this->storageScale, + $this->roundingMode + ); + } + + /** + * Returns the current number as a BigDecimal + * + * @return BigDecimal + */ + public function toNumber(): BigDecimal + { + return $this->number; + } + + /** + * Returns the current number as an integer + * + * @return int + * @throws MathException + * @throws RoundingNecessaryException + */ public function toInt(): int { return $this->number->toScale(0, $this->roundingMode)->toInt(); } + /** + * Returns the current number as a float + * + * @return float + * @throws MathException + * @throws RoundingNecessaryException + */ public function toFloat(): float { return $this->number->toScale($this->scale, $this->roundingMode)->toFloat(); } + /** + * Returns the current number as a string + * + * @return string + * @throws RoundingNecessaryException + */ public function toString(): string { return $this->number->toScale($this->scale, $this->roundingMode)->__toString(); } + /** + * Returns the current number as a string + * @return string + * @throws RoundingNecessaryException + */ public function __toString(): string { return $this->toString(); diff --git a/tests/ExampleTest.php b/tests/ExampleTest.php index 9fbc399..6a3ff3d 100644 --- a/tests/ExampleTest.php +++ b/tests/ExampleTest.php @@ -377,3 +377,12 @@ $result = $num1->sum($num2); expect($result->toString())->toBe('124.245'); // Expecting concatenated scale 4 }); + +it('can convert to storage scale', function () { + $storage_scale = 10; + $storage_value = Math::of(100.123456)->storageScale($storage_scale)->toStorageScale(); + $decode_value = Math::of($storage_value)->storageScale($storage_scale)->fromStorage()->toFloat(); + + expect($decode_value)->toBe(100.12) + ->and($storage_value)->toBe(1001200000000); +}); From 870cb62c6913d2fe69fca52482733aff6ac86ccd Mon Sep 17 00:00:00 2001 From: jon Date: Mon, 8 Jul 2024 01:20:26 +0100 Subject: [PATCH 5/9] compute percentage --- src/Helpers/Math/Math.php | 21 +++++++++++++++++++++ tests/ExampleTest.php | 7 +++++++ 2 files changed, 28 insertions(+) diff --git a/src/Helpers/Math/Math.php b/src/Helpers/Math/Math.php index 6794394..e3251fe 100644 --- a/src/Helpers/Math/Math.php +++ b/src/Helpers/Math/Math.php @@ -433,6 +433,27 @@ public function isGreaterThanOrEqual(float|int|string $value): bool return $this->compare($value) >= 0; } + /** + * Calculates the specified percentage of the current number. + * + * @param float|int|string $percentage + * @return Math + * @throws DivisionByZeroException + * @throws MathException + * @throws NumberFormatException + */ + public function toPercentageOf(float|int|string $percentage): Math + { + $percentageValue = $this->toBigDecimal($percentage)->dividedBy(100, $this->scale, $this->roundingMode); + + return self::of( + $this->number->multipliedBy($percentageValue), + $this->scale, + $this->storageScale, + $this->roundingMode + ); + } + /** * Checks if the current number is equal to another value * diff --git a/tests/ExampleTest.php b/tests/ExampleTest.php index 6a3ff3d..3919ff7 100644 --- a/tests/ExampleTest.php +++ b/tests/ExampleTest.php @@ -386,3 +386,10 @@ expect($decode_value)->toBe(100.12) ->and($storage_value)->toBe(1001200000000); }); + +it('give the percentage of the number', function () { + expect(Math::of(100)->toPercentageOf(50)->toFloat())->toBe(50.0); + expect(Math::of(100)->toPercentageOf(30)->toFloat())->toBe(30.0); + expect(Math::of(123.45)->toPercentageOf(50)->toFloat())->toBe(61.72); + expect(Math::of(99.99)->toPercentageOf(10)->toFloat())->toBe(9.99); +}); From c5347f17fda6fb5ffe817e622ff3dbbb6707c4e3 Mon Sep 17 00:00:00 2001 From: jon Date: Mon, 8 Jul 2024 01:45:20 +0100 Subject: [PATCH 6/9] avg percentageOfTotal --- src/Helpers/Math/Math.php | 93 +++++++++++++++++++++++++++++++++++++-- tests/ExampleTest.php | 30 +++++++++++++ 2 files changed, 119 insertions(+), 4 deletions(-) diff --git a/src/Helpers/Math/Math.php b/src/Helpers/Math/Math.php index e3251fe..6ab7529 100644 --- a/src/Helpers/Math/Math.php +++ b/src/Helpers/Math/Math.php @@ -46,14 +46,14 @@ public function __construct( /** * A static factory method to create a new instance of the class. * - * @param float|int|string $number + * @param float|int|string|BigDecimal $number * @param int|null $scale * @param int|null $storageScale * @param RoundingMode|null $roundingMode * @return Math */ public static function of( - float|int|string $number, + float|int|string|BigDecimal $number, ?int $scale = null, ?int $storageScale = null, ?RoundingMode $roundingMode = null @@ -67,16 +67,39 @@ public static function of( ); } + /** + * @param ...$numbers + * @return Math + * @throws DivisionByZeroException + * @throws MathException + * @throws NumberFormatException + */ + public static function average(...$numbers): Math + { + /** @var Math $sum */ + $sum = array_reduce( + $numbers, + fn($carry, $num) => self::of($carry)->sum(BigDecimal::of($num)), + BigDecimal::zero() + ); + ray('sum', $sum); + return self::of($sum->divide(count($numbers))); + } + + /** * Converts a float, int or string to a BigDecimal * - * @param float|int|string $value + * @param float|int|string|BigDecimal $value * @return BigDecimal * @throws DivisionByZeroException * @throws NumberFormatException */ - public function toBigDecimal(float|int|string $value): BigDecimal + public function toBigDecimal(float|int|string|BigDecimal $value): BigDecimal { + if($value instanceof BigDecimal){ + return $value; + } return BigDecimal::of($value); } @@ -454,6 +477,54 @@ public function toPercentageOf(float|int|string $percentage): Math ); } + /** + * Get the percentage of the current number compared to the given total + * @param float|int|string|BigDecimal $total + * @return float + * @throws DivisionByZeroException + * @throws MathException + * @throws NumberFormatException + * @throws RoundingNecessaryException + */ + public function percentageOf(float|int|string|BigDecimal $total): float + { + $percentage = $this + ->number + ->dividedBy($this->toBigDecimal($total), $this->storageScale, $this->roundingMode) + ->multipliedBy(100); + return $percentage->toScale($this->scale, $this->roundingMode)->toFloat(); + } + + + /** + * Calculates the percentage difference between two numbers + * + * @param float|int|string|BigDecimal $value + * @return float + * @throws DivisionByZeroException + * @throws MathException + * @throws NumberFormatException + */ + public function differenceInPercentage(float|int|string|BigDecimal $value): float + { + $original = $this->number; + $comparisonValue = $this->toBigDecimal($value); + $difference = $original->minus($comparisonValue)->abs(); + + if ($original->isZero() && $comparisonValue->isZero()) { + return 0.0; + } + + if ($original->isZero()) { + return 100.0; + } + + $percentage = $difference->dividedBy($original->abs(), $this->storageScale, $this->roundingMode) + ->multipliedBy(100); + + return $percentage->toScale($this->scale, $this->roundingMode)->toFloat(); + } + /** * Checks if the current number is equal to another value * @@ -590,6 +661,20 @@ public function toString(): string return $this->number->toScale($this->scale, $this->roundingMode)->__toString(); } + /** + * Formats the current number to a string + * + * @param string $thousandsSeparator + * @param string $decimalPoint + * @return string + * @throws MathException + * @throws RoundingNecessaryException + */ + public function format(string $thousandsSeparator = ',', string $decimalPoint = '.'): string + { + return number_format($this->toFloat(), $this->scale, $decimalPoint, $thousandsSeparator); + } + /** * Returns the current number as a string * @return string diff --git a/tests/ExampleTest.php b/tests/ExampleTest.php index 3919ff7..59480f1 100644 --- a/tests/ExampleTest.php +++ b/tests/ExampleTest.php @@ -393,3 +393,33 @@ expect(Math::of(123.45)->toPercentageOf(50)->toFloat())->toBe(61.72); expect(Math::of(99.99)->toPercentageOf(10)->toFloat())->toBe(9.99); }); + + +it('calculates percentage difference correctly', function () { + + expect(Math::of(100)->differenceInPercentage(50))->toBe(50.0); + expect(Math::of(50)->differenceInPercentage(100))->toBe(100.0); + + expect(Math::of(100.5)->differenceInPercentage(50.1))->toBe(50.14); + expect(Math::of(50.1)->differenceInPercentage(100.5))->toBe(100.59); +}); + +test('average calculation', function () { + // Integers + expect(Math::average(2, 3, 4, 5)->toFloat())->toBe(3.5); + expect(Math::average(0, 100)->toFloat())->toBe(50.0); + + // Floats + expect(Math::average(2.5, 3.5)->toFloat())->toBe(3.0); + expect(Math::average(3.33, 3.33)->toFloat())->toBe(3.33); +}); + +test('percentage of calculation', function () { + // Integers + expect(Math::of(50)->percentageOf(100))->toBe(50.00); + expect(Math::of(25)->percentageOf(100))->toBe(25.00); + + // Floats + expect(Math::of(1)->percentageOf(3))->toBe(33.33); + expect(Math::of(2)->percentageOf(3))->toBe(66.66); +}); From dd6dd04419c854ac75569ebf6d8d873cf3ea0e49 Mon Sep 17 00:00:00 2001 From: jon Date: Mon, 8 Jul 2024 01:49:23 +0100 Subject: [PATCH 7/9] stan level 9 pint --- phpstan.neon | 1 + src/Data/OptionData.php | 3 +- src/Helpers/Math/Math.php | 102 ++++++-------------------- src/Helpers/Math/MathOld.php | 3 +- src/Helpers/RateLimiterHelper.php | 3 +- src/LaravelHelpersServiceProvider.php | 4 +- tests/ExampleTest.php | 2 - 7 files changed, 34 insertions(+), 84 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 6fc2426..9aa1952 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -7,6 +7,7 @@ parameters: # Level 9 is the highest level level: 9 ignoreErrors: + - '#on Brick\\Math\\BigDecimal\|float\|int\|string#' scanFiles: excludePaths: - tests/*/Feature/* diff --git a/src/Data/OptionData.php b/src/Data/OptionData.php index 164ca17..601b9f8 100644 --- a/src/Data/OptionData.php +++ b/src/Data/OptionData.php @@ -18,5 +18,6 @@ public function __construct( public Optional|null|string $groupIcon = null, /** @var Collection|null */ public Optional|Collection|null $items = null, - ) {} + ) { + } } diff --git a/src/Helpers/Math/Math.php b/src/Helpers/Math/Math.php index 6ab7529..9ee152d 100644 --- a/src/Helpers/Math/Math.php +++ b/src/Helpers/Math/Math.php @@ -38,27 +38,20 @@ public function __construct( */ protected RoundingMode $roundingMode = RoundingMode::DOWN ) { - if(!$number instanceof BigDecimal){ + if (! $number instanceof BigDecimal) { $this->number = BigDecimal::of($number); } } /** * A static factory method to create a new instance of the class. - * - * @param float|int|string|BigDecimal $number - * @param int|null $scale - * @param int|null $storageScale - * @param RoundingMode|null $roundingMode - * @return Math */ public static function of( float|int|string|BigDecimal $number, ?int $scale = null, ?int $storageScale = null, ?RoundingMode $roundingMode = null - ): Math - { + ): Math { return new Math( $number, $scale ?? 2, @@ -68,7 +61,8 @@ public static function of( } /** - * @param ...$numbers + * + * @param int|float|string|BigDecimal ...$numbers * @return Math * @throws DivisionByZeroException * @throws MathException @@ -79,38 +73,37 @@ public static function average(...$numbers): Math /** @var Math $sum */ $sum = array_reduce( $numbers, - fn($carry, $num) => self::of($carry)->sum(BigDecimal::of($num)), + fn ($carry, $num) => self::of($carry)->sum(BigDecimal::of($num)), BigDecimal::zero() ); - ray('sum', $sum); + return self::of($sum->divide(count($numbers))); } - /** * Converts a float, int or string to a BigDecimal * - * @param float|int|string|BigDecimal $value - * @return BigDecimal * @throws DivisionByZeroException * @throws NumberFormatException */ public function toBigDecimal(float|int|string|BigDecimal $value): BigDecimal { - if($value instanceof BigDecimal){ + if ($value instanceof BigDecimal) { return $value; } + return BigDecimal::of($value); } /** * Sets the rounding mode up or down - * @param RoundingMode $mode + * * @return $this */ public function roundingMode(RoundingMode $mode): Math { $this->roundingMode = $mode; + return $this; } @@ -122,6 +115,7 @@ public function roundingMode(RoundingMode $mode): Math public function roundDown(): Math { $this->roundingMode = RoundingMode::DOWN; + return $this; } @@ -133,25 +127,25 @@ public function roundDown(): Math public function roundUp(): Math { $this->roundingMode = RoundingMode::UP; + return $this; } /** * Sets the scale of the number * - * @param int $scale * @return $this */ public function scale(int $scale): Math { $this->scale = $scale; + return $this; } /** * Sets the storage scale of the number * - * @param int $storageScale * @return $this */ public function storageScale(int $storageScale): Math @@ -164,8 +158,6 @@ public function storageScale(int $storageScale): Math /** * Adds a value to the current number * - * @param float|int|string $value - * @return self * @throws DivisionByZeroException * @throws NumberFormatException * @throws MathException @@ -183,8 +175,6 @@ public function sum(float|int|string $value): Math /** * Subtracts a value from the current number * - * @param float|int|string $value - * @return self * @throws DivisionByZeroException * @throws NumberFormatException * @throws MathException @@ -202,8 +192,6 @@ public function subtract(float|int|string $value): Math /** * Multiplies the current number by a value * - * @param float|int|string $value - * @return self * @throws DivisionByZeroException * @throws NumberFormatException * @throws MathException @@ -221,8 +209,6 @@ public function multiply(float|int|string $value): Math /** * Divides the current number by a value * - * @param float|int|string $value - * @return self * @throws DivisionByZeroException * @throws MathException * @throws NumberFormatException @@ -239,9 +225,6 @@ public function divide(float|int|string $value): Math /** * Raises the current number to the power of an exponent - * - * @param int $exponent - * @return self */ public function pow(int $exponent): Math { @@ -256,9 +239,6 @@ public function pow(int $exponent): Math /** * Rounds the current number to the given precision * - * @param int $precision - * @param RoundingMode|null $roundingMode - * @return self * @throws RoundingNecessaryException */ public function round(int $precision = 0, ?RoundingMode $roundingMode = null): Math @@ -274,7 +254,6 @@ public function round(int $precision = 0, ?RoundingMode $roundingMode = null): M /** * Rounds the current number up to the nearest * - * @return self * @throws MathException */ public function ceil(): Math @@ -290,7 +269,6 @@ public function ceil(): Math /** * Rounds the current number down to the nearest * - * @return self * @throws MathException */ public function floor(): Math @@ -305,8 +283,6 @@ public function floor(): Math /** * Returns the absolute value of the current number - * - * @return self */ public function absolute(): Math { @@ -325,7 +301,7 @@ public function absolute(): Math */ public function negative(): Math { - if($this->number->isNegative()){ + if ($this->number->isNegative()) { return $this; } @@ -340,8 +316,6 @@ public function negative(): Math /** * Adds a percentage to the current number * - * @param float|int|string $percentage - * @return Math * @throws DivisionByZeroException * @throws MathException * @throws NumberFormatException @@ -364,8 +338,6 @@ public function addPercentage(float|int|string $percentage): Math /** * Subtracts a percentage from the current number * - * @param float|int|string $percentage - * @return Math * @throws DivisionByZeroException * @throws MathException * @throws NumberFormatException @@ -388,8 +360,6 @@ public function subtractPercentage(float|int|string $percentage): Math /** * Compares the current number to another value * - * @param float|int|string $value - * @return int * @throws MathException * @throws RoundingNecessaryException */ @@ -401,14 +371,13 @@ public function compare(float|int|string $value): int $this->storageScale, $this->roundingMode ); + return $this->number->compareTo($other->number->toBigDecimal()); } /** * Checks if the current number is less than another value * - * @param float|int|string $value - * @return bool * @throws MathException * @throws RoundingNecessaryException */ @@ -420,8 +389,6 @@ public function isLessThan(float|int|string $value): bool /** * Checks if the current number is less than or equal to another value * - * @param float|int|string $value - * @return bool * @throws MathException * @throws RoundingNecessaryException */ @@ -433,8 +400,6 @@ public function isLessThanOrEqual(float|int|string $value): bool /** * Checks if the current number is greater than another value * - * @param float|int|string $value - * @return bool * @throws MathException * @throws RoundingNecessaryException */ @@ -446,8 +411,6 @@ public function isGreaterThan(float|int|string $value): bool /** * Checks if the current number is greater than or equal to another value * - * @param float|int|string $value - * @return bool * @throws MathException * @throws RoundingNecessaryException */ @@ -459,8 +422,6 @@ public function isGreaterThanOrEqual(float|int|string $value): bool /** * Calculates the specified percentage of the current number. * - * @param float|int|string $percentage - * @return Math * @throws DivisionByZeroException * @throws MathException * @throws NumberFormatException @@ -479,8 +440,7 @@ public function toPercentageOf(float|int|string $percentage): Math /** * Get the percentage of the current number compared to the given total - * @param float|int|string|BigDecimal $total - * @return float + * * @throws DivisionByZeroException * @throws MathException * @throws NumberFormatException @@ -492,15 +452,13 @@ public function percentageOf(float|int|string|BigDecimal $total): float ->number ->dividedBy($this->toBigDecimal($total), $this->storageScale, $this->roundingMode) ->multipliedBy(100); + return $percentage->toScale($this->scale, $this->roundingMode)->toFloat(); } - /** * Calculates the percentage difference between two numbers * - * @param float|int|string|BigDecimal $value - * @return float * @throws DivisionByZeroException * @throws MathException * @throws NumberFormatException @@ -528,8 +486,6 @@ public function differenceInPercentage(float|int|string|BigDecimal $value): floa /** * Checks if the current number is equal to another value * - * @param float|int|string $value - * @return bool * @throws MathException * @throws RoundingNecessaryException */ @@ -541,7 +497,6 @@ public function isEqual(float|int|string $value): bool /** * Checks if the current number is zero * - * @return bool * @throws MathException * @throws RoundingNecessaryException */ @@ -553,19 +508,17 @@ public function isZero(): bool /** * Checks if the current number is not zero * - * @return bool * @throws MathException * @throws RoundingNecessaryException */ public function isNotZero(): bool { - return !$this->isZero(); + return ! $this->isZero(); } /** * Ensures the scale of the current number * - * @return Math * @throws RoundingNecessaryException */ public function ensureScale(): Math @@ -581,7 +534,6 @@ public function ensureScale(): Math /** * Returns the current number as an integer to save on storage * - * @return int * @throws MathException * @throws RoundingNecessaryException */ @@ -599,12 +551,11 @@ public function toStorageScale(): int /** * Returns the current number from the storage as a Math object * - * @return Math * @throws DivisionByZeroException * @throws MathException * @throws NumberFormatException */ - function fromStorage(): Math + public function fromStorage(): Math { $decimalPlaces = BigDecimal::of(10)->power($this->storageScale); @@ -618,18 +569,18 @@ function fromStorage(): Math /** * Returns the current number as a BigDecimal - * - * @return BigDecimal */ public function toNumber(): BigDecimal { - return $this->number; + if($this->number instanceof BigDecimal){ + return $this->number; + } + return BigDecimal::of($this->number); } /** * Returns the current number as an integer * - * @return int * @throws MathException * @throws RoundingNecessaryException */ @@ -641,7 +592,6 @@ public function toInt(): int /** * Returns the current number as a float * - * @return float * @throws MathException * @throws RoundingNecessaryException */ @@ -653,7 +603,6 @@ public function toFloat(): float /** * Returns the current number as a string * - * @return string * @throws RoundingNecessaryException */ public function toString(): string @@ -664,9 +613,6 @@ public function toString(): string /** * Formats the current number to a string * - * @param string $thousandsSeparator - * @param string $decimalPoint - * @return string * @throws MathException * @throws RoundingNecessaryException */ @@ -677,7 +623,7 @@ public function format(string $thousandsSeparator = ',', string $decimalPoint = /** * Returns the current number as a string - * @return string + * * @throws RoundingNecessaryException */ public function __toString(): string diff --git a/src/Helpers/Math/MathOld.php b/src/Helpers/Math/MathOld.php index ae42622..fdf9701 100644 --- a/src/Helpers/Math/MathOld.php +++ b/src/Helpers/Math/MathOld.php @@ -21,7 +21,8 @@ class MathOld public function __construct( protected int $floatScale, protected int $integerScale = 20, - ) {} + ) { + } /** * Converts a float into a integer based on the given scale diff --git a/src/Helpers/RateLimiterHelper.php b/src/Helpers/RateLimiterHelper.php index 4ad06c1..e79129a 100644 --- a/src/Helpers/RateLimiterHelper.php +++ b/src/Helpers/RateLimiterHelper.php @@ -29,7 +29,8 @@ public function __construct( protected string $key, protected string $by, protected bool $hashed = true - ) {} + ) { + } /** * Forwards the call to the RateLimiter instance. diff --git a/src/LaravelHelpersServiceProvider.php b/src/LaravelHelpersServiceProvider.php index 023f0b5..eef3c12 100644 --- a/src/LaravelHelpersServiceProvider.php +++ b/src/LaravelHelpersServiceProvider.php @@ -19,5 +19,7 @@ public function bootingPackage(): void // Booting the package } - public function registeringPackage(): void {} + public function registeringPackage(): void + { + } } diff --git a/tests/ExampleTest.php b/tests/ExampleTest.php index 59480f1..f8ba298 100644 --- a/tests/ExampleTest.php +++ b/tests/ExampleTest.php @@ -237,7 +237,6 @@ ->toString() )->toBe('36.00'); - // Operations with negative numbers expect(Math::of(-5)->subtract(3)->toFloat())->toBe(-8.00); expect(Math::of(-5)->multiply(-2)->toFloat())->toBe(10.00); @@ -394,7 +393,6 @@ expect(Math::of(99.99)->toPercentageOf(10)->toFloat())->toBe(9.99); }); - it('calculates percentage difference correctly', function () { expect(Math::of(100)->differenceInPercentage(50))->toBe(50.0); From 10fd5c4a5886a1f7b9cc22ab8233fe893a62017a Mon Sep 17 00:00:00 2001 From: jon Date: Mon, 8 Jul 2024 11:36:17 +0100 Subject: [PATCH 8/9] fixed --- config/helpers.php | 8 +- src/Helpers/Math/Math.php | 21 +- src/Helpers/Math/MathContract.php | 50 ---- src/Helpers/Math/MathOld.php | 467 ------------------------------ tests/ExampleTest.php | 26 ++ 5 files changed, 44 insertions(+), 528 deletions(-) delete mode 100644 src/Helpers/Math/MathContract.php delete mode 100644 src/Helpers/Math/MathOld.php diff --git a/config/helpers.php b/config/helpers.php index 382cf51..1e94c4a 100644 --- a/config/helpers.php +++ b/config/helpers.php @@ -1,12 +1,18 @@ [ + 'scale' => 10, + 'storage_scale' => 10, + 'rounding_mode' => \Brick\Math\RoundingMode::DOWN, + ] ]; diff --git a/src/Helpers/Math/Math.php b/src/Helpers/Math/Math.php index 9ee152d..8c58ac5 100644 --- a/src/Helpers/Math/Math.php +++ b/src/Helpers/Math/Math.php @@ -36,7 +36,7 @@ public function __construct( * * @var RoundingMode $roundingMode */ - protected RoundingMode $roundingMode = RoundingMode::DOWN + public RoundingMode $roundingMode = RoundingMode::DOWN ) { if (! $number instanceof BigDecimal) { $this->number = BigDecimal::of($number); @@ -52,11 +52,12 @@ public static function of( ?int $storageScale = null, ?RoundingMode $roundingMode = null ): Math { + return new Math( $number, - $scale ?? 2, - $storageScale ?? 10, - $roundingMode ?? RoundingMode::DOWN + $scale ?? config('helpers.math.scale', 10), + $storageScale ?? config('helpers.math.scale', 10), + $roundingMode ?? config('helpers.math.rounding_mode', RoundingMode::DOWN) ); } @@ -448,10 +449,9 @@ public function toPercentageOf(float|int|string $percentage): Math */ public function percentageOf(float|int|string|BigDecimal $total): float { - $percentage = $this - ->number - ->dividedBy($this->toBigDecimal($total), $this->storageScale, $this->roundingMode) - ->multipliedBy(100); + $percentage = $this->number + ->multipliedBy(BigDecimal::of(100)) + ->dividedBy($this->toBigDecimal($total), $this->scale, $this->roundingMode); return $percentage->toScale($this->scale, $this->roundingMode)->toFloat(); } @@ -477,8 +477,9 @@ public function differenceInPercentage(float|int|string|BigDecimal $value): floa return 100.0; } - $percentage = $difference->dividedBy($original->abs(), $this->storageScale, $this->roundingMode) - ->multipliedBy(100); + $percentage = $difference + ->multipliedBy(100) + ->dividedBy($original->abs(), $this->scale, $this->roundingMode); return $percentage->toScale($this->scale, $this->roundingMode)->toFloat(); } diff --git a/src/Helpers/Math/MathContract.php b/src/Helpers/Math/MathContract.php deleted file mode 100644 index 457c6ac..0000000 --- a/src/Helpers/Math/MathContract.php +++ /dev/null @@ -1,50 +0,0 @@ -powTen($this->floatScale); - - return $this->round( - $this->mul( - $value, - $decimalPlaces, - $this->floatScale - ) - ); - } - - /** - * Converts a big integer into a float based on the given scale - * - * @throws MathException - */ - public function intToFloat(float|int|string $value): string - { - $decimalPlaces = $this->powTen($this->floatScale); - - return $this->div($value, $decimalPlaces, $this->floatScale); - } - - /** - * Sums to big integers - * - * @throws MathException - * @throws RoundingNecessaryException - */ - public function addInteger(float|int|string $first, float|int|string $second, ?int $scale = null): string - { - return $this->add( - $this->floatToInt($first), - $this->floatToInt($second), - ); - } - - /** - * Adds a percentage to a big integer - * - * @throws MathException - * @throws RoundingNecessaryException - */ - public function addPercentageInteger(float|int|string $number, float|int|string $percentage, ?int $scale = null): string - { - $intNumber = $this->floatToInt($number); - $percentageValue = $this->div($this->mul($intNumber, $this->floatToInt($percentage)), $this->floatToInt(100), $scale); - - return $this->add($intNumber, $percentageValue); - } - - /** - * Subtracts two big integers - * - * @throws MathException - * @throws RoundingNecessaryException - */ - public function subInteger(float|int|string $first, float|int|string $second, ?int $scale = null): string - { - return $this->sub( - $this->floatToInt($first), - $this->floatToInt($second) - ); - } - - /** - * Subtracts a percentage from a big integer - * - * @throws MathException - * @throws RoundingNecessaryException - */ - public function subtractPercentageInteger(float|int|string $number, float|int|string $percentage, ?int $scale = null): string - { - $intNumber = $this->floatToInt($number); - $percentageValue = $this->div($this->mul($intNumber, $this->floatToInt($percentage)), $this->floatToInt(100), $scale); - - return $this->sub($intNumber, $percentageValue); - } - - /** - * Divides two big integers - * - * @throws MathException - */ - public function divInteger(float|int|string $first, float|int|string $second, ?int $scale = null): string - { - return $this->div( - $this->floatToInt($first), - $this->floatToInt($second), - $scale ?? $this->floatScale - ); - } - - /** - * Multiplies two big integers - * - * @throws MathException - * @throws RoundingNecessaryException - */ - public function mulInteger(float|int|string $first, float|int|string $second, ?int $scale = null): string - { - return $this->mul( - $this->floatToInt($first), - $this->floatToInt($second) - ); - } - - /** - * Raises a big integer to the power of another big integer - * - * @throws MathException - * @throws RoundingNecessaryException - */ - public function powInteger(float|int|string $first, float|int|string $second, ?int $scale = null): string - { - return $this->pow( - $this->floatToInt($first), - $this->floatToInt($second), - ); - } - - /** - * Powers a big integer to the power of ten - * - * @throws MathException - * @throws RoundingNecessaryException - */ - public function powTenInteger(float|int|string $number): string - { - return $this->powTen($this->floatToInt($number)); - } - - /** - * Ceils a big integer - * - * @throws MathException - */ - public function ceilInteger(float|int|string $number): string - { - return $this->ceil($this->floatToInt($number)); - } - - /** - * Floors a big integer - * - * @throws MathException - */ - public function floorInteger(float|int|string $number): string - { - return $this->floor($this->floatToInt($number)); - } - - /** - * Rounds a big integer - * - * @throws MathException - */ - public function roundInteger(float|int|string $number, int $precision = 0): string - { - return $this->round($this->floatToInt($number), $precision); - } - - /** - * Absolutes a big integer - * - * @throws MathException - * @throws RoundingNecessaryException - */ - public function absInteger(float|int|string $number): string - { - return $this->abs($this->floatToInt($number)); - } - - /** - * Negatives a big integer - * - * @throws MathException - * @throws RoundingNecessaryException - */ - public function negativeInteger(float|int|string $number): string - { - return $this->negative($this->floatToInt($number)); - } - - /** - * Compares two big integers - * - * @throws MathException - */ - public function compareInteger(float|int|string $first, float|int|string $second): int - { - return $this->compare($this->floatToInt($first), $this->floatToInt($second)); - } - - /** - * Ensures the scale of a big integer - * - * @throws MathException - * @throws RoundingNecessaryException - */ - public function ensureScale(float|int|string $number): string - { - return $this->mul($number, 1); - } - - /** - * Sums two floats - * - * @throws MathException - * @throws RoundingNecessaryException - */ - public function add(float|int|string $first, float|int|string $second, ?int $scale = null): string - { - return (string) BigDecimal::of($first) - ->plus(BigDecimal::of($second)) - ->toScale($scale ?? $this->floatScale, RoundingMode::DOWN); - } - - /** - * Adds a percentage to a number - * - * @throws MathException - * @throws RoundingNecessaryException - */ - public function addPercentage(float|int|string $number, float|int|string $percentage, ?int $scale = null): string - { - $percentageValue = $this->div($this->mul($number, $percentage), 100, $scale); - - return $this->add($number, $percentageValue, $scale); - } - - /** - * Subtracts two floats - * - * @throws MathException - * @throws RoundingNecessaryException - */ - public function sub(float|int|string $first, float|int|string $second, ?int $scale = null): string - { - return (string) BigDecimal::of($first) - ->minus(BigDecimal::of($second)) - ->toScale($scale ?? $this->floatScale, RoundingMode::DOWN); - } - - /** - * Subtracts a percentage from a number - * - * @throws MathException - * @throws RoundingNecessaryException - */ - public function subtractPercentage(float|int|string $number, float|int|string $percentage, ?int $scale = null): string - { - $percentageValue = $this->div($this->mul($number, $percentage), 100, $scale); - - return $this->sub($number, $percentageValue, $scale); - } - - /** - * Divides two floats - * - * @throws MathException - */ - public function div(float|int|string $first, float|int|string $second, ?int $scale = null): string - { - return (string) BigDecimal::of($first) - ->dividedBy(BigDecimal::of($second), $scale ?? $this->floatScale, RoundingMode::DOWN); - } - - /** - * Multiplies two floats - * - * @throws MathException - * @throws RoundingNecessaryException - */ - public function mul(float|int|string $first, float|int|string $second, ?int $scale = null): string - { - return (string) BigDecimal::of($first) - ->multipliedBy(BigDecimal::of($second)) - ->toScale($scale ?? $this->floatScale, RoundingMode::DOWN); - } - - /** - * Raises a float to the power of another float - * - * @throws MathException - * @throws RoundingNecessaryException - */ - public function pow(float|int|string $first, float|int|string $second, ?int $scale = null): string - { - return (string) BigDecimal::of($first) - ->power((int) $second) - ->toScale($scale ?? $this->floatScale, RoundingMode::DOWN); - } - - /** - * Powers a float to the power of ten - * - * @throws MathException - * @throws RoundingNecessaryException - */ - public function powTen(float|int|string $number): string - { - return $this->pow(10, $number); - } - - /** - * Ceils a float - * - * @throws MathException - */ - public function ceil(float|int|string $number): string - { - return (string) BigDecimal::of($number) - ->dividedBy(BigDecimal::one(), 0, RoundingMode::CEILING); - } - - /** - * Floors a float - * - * @throws MathException - */ - public function floor(float|int|string $number): string - { - return (string) BigDecimal::of($number) - ->dividedBy(BigDecimal::one(), 0, RoundingMode::FLOOR); - } - - /** - * Rounds a float - * - * @throws MathException - */ - public function round(float|int|string $number, int $precision = 0): string - { - return (string) BigDecimal::of($number) - ->dividedBy(BigDecimal::one(), $precision, RoundingMode::HALF_UP); - } - - /** - * Absolutes a float - * - * @throws MathException - * @throws RoundingNecessaryException - */ - public function abs(float|int|string $number, ?int $scale = null): string - { - return (string) BigDecimal::of($number)->abs()->toScale($scale ?? $this->floatScale, RoundingMode::DOWN); - } - - /** - * Negatives a float - * - * @throws MathException - * @throws RoundingNecessaryException - */ - public function negative(float|int|string $number, ?int $scale = null): string - { - $number = BigDecimal::of($number); - if ($number->isNegative()) { - return (string) $number->toScale($scale ?? $this->floatScale, RoundingMode::DOWN); - } - - return (string) BigDecimal::of($number) - ->toScale($scale ?? $this->floatScale, RoundingMode::DOWN) - ->negated(); - } - - /** - * Compares two floats - * - * @throws MathException - */ - public function compare(float|int|string $first, float|int|string $second): int - { - return BigDecimal::of($first)->compareTo(BigDecimal::of($second)); - } - - /** - * Check if its zero - * - * @throws DivisionByZeroException - * @throws NumberFormatException - * @throws RoundingNecessaryException - */ - public function isZero(float|int|string $number, ?int $scale = null): bool - { - return BigDecimal::of($number) - ->toScale($scale ?? $this->floatScale, RoundingMode::DOWN) - ->isZero(); - } - - /** - * Check if its not zero - * - * @throws DivisionByZeroException - * @throws NumberFormatException - * @throws RoundingNecessaryException - */ - public function isNotZero(float|int|string $number, ?int $scale = null): bool - { - return ! $this->isZero($number, $scale); - } - - /** - * Returns the representation of the number as a string - * - * @throws DivisionByZeroException - * @throws NumberFormatException - * @throws RoundingNecessaryException - */ - public function toToNumber(float|int|string $number, ?int $scale = null): BigDecimal - { - return BigDecimal::of($number) - ->toScale($scale ?? $this->floatScale, RoundingMode::DOWN); - } - - /** - * Check if two numbers are equal - * - * @throws DivisionByZeroException - * @throws NumberFormatException - * @throws RoundingNecessaryException - */ - public function isEqual(float|int|string $first, float|int|string $second, ?int $scale = null): bool - { - $firstScaled = BigDecimal::of($first) - ->toScale($scale ?? $this->floatScale, RoundingMode::DOWN); - - $secondScaled = BigDecimal::of($second) - ->toScale($scale ?? $this->floatScale, RoundingMode::DOWN); - - return $firstScaled->isEqualTo($secondScaled); - } -} diff --git a/tests/ExampleTest.php b/tests/ExampleTest.php index f8ba298..8656ad3 100644 --- a/tests/ExampleTest.php +++ b/tests/ExampleTest.php @@ -5,6 +5,12 @@ use Brick\Math\RoundingMode; use Flavorly\LaravelHelpers\Helpers\Math\Math; +beforeEach(function () { + config()->set('helpers.math.scale', 2); + config()->set('helpers.math.storage_scale', 10); + config()->set('helpers.math.rounding_mode', RoundingMode::DOWN); +}); + it('performs basic sum operations', function ($initial, $addend, $expected, $scale = null) { $math = Math::of($initial, $scale); $result = $math->sum($addend)->toFloat(); @@ -421,3 +427,23 @@ expect(Math::of(1)->percentageOf(3))->toBe(33.33); expect(Math::of(2)->percentageOf(3))->toBe(66.66); }); + +it('respects config scale and rounding mode', function () { + // Mock config values + config(['helpers.math.scale' => 4]); + config(['helpers.math.rounding_mode' => RoundingMode::UP]); + + $math = Math::of(1.23456789); + + expect($math->toFloat())->toBe(1.2346); + expect($math->roundingMode)->toBe(RoundingMode::UP); + + // Reset config to default + config(['helpers.math.scale' => 2]); + config(['helpers.math.rounding_mode' => RoundingMode::DOWN]); + + $defaultMath = Math::of(1.23456789); + + expect($defaultMath->toFloat())->toBe(1.23); + expect($defaultMath->roundingMode)->toBe(RoundingMode::DOWN); +}); From 6a1264ccd2db9b792276ae842ca5035539478cd9 Mon Sep 17 00:00:00 2001 From: jon Date: Mon, 8 Jul 2024 11:39:05 +0100 Subject: [PATCH 9/9] stan 9 --- config/helpers.php | 3 +-- src/Helpers/Math/Math.php | 13 ++++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/config/helpers.php b/config/helpers.php index 1e94c4a..5ba00e5 100644 --- a/config/helpers.php +++ b/config/helpers.php @@ -1,6 +1,5 @@ 10, 'storage_scale' => 10, 'rounding_mode' => \Brick\Math\RoundingMode::DOWN, - ] + ], ]; diff --git a/src/Helpers/Math/Math.php b/src/Helpers/Math/Math.php index 8c58ac5..4f3ce02 100644 --- a/src/Helpers/Math/Math.php +++ b/src/Helpers/Math/Math.php @@ -55,16 +55,18 @@ public static function of( return new Math( $number, - $scale ?? config('helpers.math.scale', 10), - $storageScale ?? config('helpers.math.scale', 10), + // @phpstan-ignore-next-line + $scale ?? (int) config('helpers.math.scale', 10), + // @phpstan-ignore-next-line + $storageScale ?? (int) config('helpers.math.scale', 10), + // @phpstan-ignore-next-line $roundingMode ?? config('helpers.math.rounding_mode', RoundingMode::DOWN) ); } /** - * * @param int|float|string|BigDecimal ...$numbers - * @return Math + * * @throws DivisionByZeroException * @throws MathException * @throws NumberFormatException @@ -573,9 +575,10 @@ public function fromStorage(): Math */ public function toNumber(): BigDecimal { - if($this->number instanceof BigDecimal){ + if ($this->number instanceof BigDecimal) { return $this->number; } + return BigDecimal::of($this->number); }