expectException(\DivisionByZeroError::class); Bc::div('1', '0'); } public function testDivByZeroDecimalThrows(): void { $this->expectException(\DivisionByZeroError::class); Bc::div('1', '0.00000000'); } // ── scale ───────────────────────────────────────────────────────────────── public function testScalePadsShortDecimals(): void { self::assertSame('1.50000000', Bc::scale('1.5')); } public function testScaleTruncatesLongDecimals(): void { self::assertSame('1.12345678', Bc::scale('1.123456789')); } public function testScaleIntegerInput(): void { self::assertSame('42.00000000', Bc::scale('42')); } public function testScaleCustomScale(): void { self::assertSame('3.14', Bc::scale('3.14159', 2)); } // ── abs ─────────────────────────────────────────────────────────────────── public function testAbsNegativeValue(): void { self::assertSame('5.00000000', Bc::abs('-5')); } public function testAbsPositiveValueUnchanged(): void { self::assertSame('3.14000000', Bc::abs('3.14')); } public function testAbsZero(): void { self::assertSame('0.00000000', Bc::abs('0')); } // ── max / min ───────────────────────────────────────────────────────────── public function testMaxReturnsLarger(): void { self::assertSame('10.00000000', Bc::max('10', '5')); } public function testMaxWhenEqual(): void { self::assertSame('5.00000000', Bc::max('5', '5')); } public function testMinReturnsSmaller(): void { self::assertSame('3.00000000', Bc::min('10', '3')); } public function testMinNegativeValues(): void { self::assertSame('-10.00000000', Bc::min('-5', '-10')); } // ── compare ─────────────────────────────────────────────────────────────── public function testCompareGreater(): void { self::assertSame(1, Bc::compare('10', '5')); } public function testCompareLess(): void { self::assertSame(-1, Bc::compare('5', '10')); } public function testCompareEqual(): void { self::assertSame(0, Bc::compare('5.00', '5.000')); } // ── isZero ──────────────────────────────────────────────────────────────── public function testIsZeroTrueForZero(): void { self::assertTrue(Bc::isZero('0')); } public function testIsZeroTrueForFormattedZero(): void { self::assertTrue(Bc::isZero('0.00000000')); } public function testIsZeroFalseForNonZero(): void { self::assertFalse(Bc::isZero('0.00000001')); } // ── gt / lt ─────────────────────────────────────────────────────────────── public function testGtTrueWhenGreater(): void { self::assertTrue(Bc::gt('10', '5')); } public function testGtFalseWhenEqual(): void { self::assertFalse(Bc::gt('5', '5')); } public function testLtTrueWhenLess(): void { self::assertTrue(Bc::lt('3', '7')); } public function testLtFalseWhenGreater(): void { self::assertFalse(Bc::lt('7', '3')); } // ── gte ─────────────────────────────────────────────────────────────────── public function testGteTrueWhenGreater(): void { self::assertTrue(Bc::gte('10', '5')); } public function testGteTrueWhenEqual(): void { self::assertTrue(Bc::gte('5', '5')); } public function testGteFalseWhenLess(): void { self::assertFalse(Bc::gte('3', '7')); } // ── lte ─────────────────────────────────────────────────────────────────── public function testLteTrueWhenLess(): void { self::assertTrue(Bc::lte('3', '7')); } public function testLteTrueWhenEqual(): void { self::assertTrue(Bc::lte('5', '5')); } public function testLteFalseWhenGreater(): void { self::assertFalse(Bc::lte('10', '5')); } // ── eq ──────────────────────────────────────────────────────────────────── public function testEqTrueWhenEqual(): void { self::assertTrue(Bc::eq('5.00', '5.000')); } public function testEqFalseWhenDifferent(): void { self::assertFalse(Bc::eq('5', '5.00000001')); } public function testEqRespectsScale(): void { // At scale 2, 5.001 and 5.009 both truncate to 5.00 and are equal self::assertTrue(Bc::eq('5.001', '5.009', 2)); } // ── isPositive ──────────────────────────────────────────────────────────── public function testIsPositiveTrueForPositiveValue(): void { self::assertTrue(Bc::isPositive('0.00000001')); } public function testIsPositiveFalseForZero(): void { self::assertFalse(Bc::isPositive('0')); } public function testIsPositiveFalseForNegative(): void { self::assertFalse(Bc::isPositive('-1')); } // ── isNegative ──────────────────────────────────────────────────────────── public function testIsNegativeTrueForNegativeValue(): void { self::assertTrue(Bc::isNegative('-0.00000001')); } public function testIsNegativeFalseForZero(): void { self::assertFalse(Bc::isNegative('0')); } public function testIsNegativeFalseForPositive(): void { self::assertFalse(Bc::isNegative('1')); } // ── negate ──────────────────────────────────────────────────────────────── public function testNegatePositiveValue(): void { self::assertSame('-5.00000000', Bc::negate('5')); } public function testNegateNegativeValue(): void { self::assertSame('5.00000000', Bc::negate('-5')); } public function testNegateZeroRemainsZero(): void { self::assertSame('0.00000000', Bc::negate('0')); } public function testNegateCustomScale(): void { self::assertSame('-3.14', Bc::negate('3.14', 2)); } // ── sum ─────────────────────────────────────────────────────────────────── public function testSumEmptyArrayReturnsZero(): void { self::assertSame('0.00000000', Bc::sum([])); } public function testSumSingleElement(): void { self::assertSame('5.00000000', Bc::sum(['5'])); } public function testSumMultipleElements(): void { self::assertSame('6.00000000', Bc::sum(['1', '2', '3'])); } public function testSumWithNegativeValues(): void { self::assertSame('-2.00000000', Bc::sum(['-1', '2', '-3'])); } public function testSumCustomScale(): void { self::assertSame('3.30', Bc::sum(['1.1', '2.2'], 2)); } public function testSumThrowsOnNonNumericElement(): void { $this->expectException(\InvalidArgumentException::class); /** @phpstan-ignore-next-line */ Bc::sum(['1', 'not-a-number', '3']); } // ── product ─────────────────────────────────────────────────────────────── public function testProductEmptyArrayReturnsOne(): void { self::assertSame('1.00000000', Bc::product([])); } public function testProductSingleElement(): void { self::assertSame('7.00000000', Bc::product(['7'])); } public function testProductMultipleElements(): void { self::assertSame('24.00000000', Bc::product(['2', '3', '4'])); } public function testProductWithDecimalValues(): void { self::assertSame('0.25000000', Bc::product(['0.5', '0.5'])); } public function testProductWithNegativeValue(): void { self::assertSame('-6.00000000', Bc::product(['-2', '3'])); } // ── subAll ──────────────────────────────────────────────────────────────── public function testSubAllEmptyArrayReturnsZero(): void { self::assertSame('0.00000000', Bc::subAll([])); } public function testSubAllSingleElement(): void { // Single element is returned as-is (no reduce step applies) self::assertSame('5', Bc::subAll(['5'])); } public function testSubAllMultipleElements(): void { // 10 - 3 - 2 = 5 self::assertSame('5.00000000', Bc::subAll(['10', '3', '2'])); } public function testSubAllResultCanBeNegative(): void { self::assertSame('-5.00000000', Bc::subAll(['5', '10'])); } // ── divAll ──────────────────────────────────────────────────────────────── public function testDivAllEmptyArrayReturnsOne(): void { self::assertSame('1.00000000', Bc::divAll([])); } public function testDivAllSingleElement(): void { // Single element is returned as-is (no reduce step applies) self::assertSame('8', Bc::divAll(['8'])); } public function testDivAllMultipleElements(): void { // 100 / 5 / 4 = 5 self::assertSame('5.00000000', Bc::divAll(['100', '5', '4'])); } public function testDivAllThrowsOnDivisionByZero(): void { $this->expectException(\DivisionByZeroError::class); Bc::divAll(['10', '0']); } // ── percent ─────────────────────────────────────────────────────────────── public function testPercentBasicCalculation(): void { // 15% of 200 = 30 self::assertSame('30.00000000', Bc::percent('200', '15')); } public function testPercentFiftyPercent(): void { self::assertSame('50.00000000', Bc::percent('100', '50')); } public function testPercentFractionalResult(): void { // 33.33% of 100 = 33.33000000 self::assertSame('33.33000000', Bc::percent('100', '33.33')); } public function testPercentZeroPercent(): void { self::assertSame('0.00000000', Bc::percent('100', '0')); } public function testPercentCustomScale(): void { self::assertSame('30.00', Bc::percent('200', '15', 2)); } // ── Guard validation ────────────────────────────────────────────────────── public function testAddThrowsOnNonNumericInput(): void { $this->expectException(\InvalidArgumentException::class); /** @phpstan-ignore-next-line */ Bc::add('not-a-number', '1'); } public function testSubThrowsOnNonNumericInput(): void { $this->expectException(\InvalidArgumentException::class); /** @phpstan-ignore-next-line */ Bc::sub('5', 'abc'); } public function testMulThrowsOnNonNumericInput(): void { $this->expectException(\InvalidArgumentException::class); /** @phpstan-ignore-next-line */ Bc::mul('x', '2'); } public function testDivThrowsOnNonNumericInput(): void { $this->expectException(\InvalidArgumentException::class); /** @phpstan-ignore-next-line */ Bc::div('10', 'y'); } public function testGtThrowsOnNonNumericInput(): void { $this->expectException(\InvalidArgumentException::class); /** @phpstan-ignore-next-line */ Bc::gt('bad', '1'); } public function testLtThrowsOnNonNumericInput(): void { $this->expectException(\InvalidArgumentException::class); /** @phpstan-ignore-next-line */ Bc::lt('1', 'bad'); } }