diff --git a/CHANGELOG.md b/CHANGELOG.md index 1099cbe..b868d9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 2.7.1 under development -- no changes in this release. +- New #156: Add `NumericHelper::trimDecimalZeros()` (@samdark, @vjik) ## 2.7.0 November 23, 2025 diff --git a/README.md b/README.md index 2d16529..d768fed 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,7 @@ The following methods are available: - normalize - isInteger - convertHumanReadableSizeToBytes +- trimDecimalZeros ## Inflector usage diff --git a/src/NumericHelper.php b/src/NumericHelper.php index 7380ad8..bf68d64 100644 --- a/src/NumericHelper.php +++ b/src/NumericHelper.php @@ -122,7 +122,7 @@ public static function isInteger(mixed $value): bool } /** - * Converts human readable size to bytes. + * Converts human-readable size to bytes. * * @param string $string Human readable size. Examples: `1024`, `1kB`, `1.5M`, `1GiB`. Full * list of supported postfixes in {@see FILESYSTEM_SIZE_POSTFIXES}. @@ -162,4 +162,41 @@ public static function convertHumanReadableSizeToBytes(string $string): int throw new InvalidArgumentException("Incorrect input string: $string"); } + + /** + * Trims spaces and trailing decimal zeros from a numeric string. + * + * If the fractional part consists only of zeros, the decimal dot separator is removed as well. + * The value that is `null` is returned as-is. + * + * @param string|null $value Numeric string or null. + * + * @return string|null The input string with spaces, trailing decimal zeros (and a trailing decimal + * dot separator, if any) removed, or `null` if the input was `null`. + * + * @see is_numeric() + */ + public static function trimDecimalZeros(?string $value): ?string + { + if ($value === null) { + return null; + } + + if (!is_numeric($value)) { + throw new InvalidArgumentException( + sprintf('Value must be numeric string or null. "%s" given.', $value) + ); + } + + $value = trim($value); + + if (!str_contains($value, '.')) { + return $value; + } + + $value = rtrim($value, '0'); + $value = rtrim($value, '.'); + + return $value ?: '0'; + } } diff --git a/tests/NumericHelperTest.php b/tests/NumericHelperTest.php index 79a02a7..460bfce 100644 --- a/tests/NumericHelperTest.php +++ b/tests/NumericHelperTest.php @@ -4,6 +4,7 @@ namespace Yiisoft\Strings\Tests; +use InvalidArgumentException; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Yiisoft\Strings\NumericHelper; @@ -35,7 +36,7 @@ public function testToOrdinal(): void public function testToOrdinalWithIncorrectType(): void { - $this->expectException(\InvalidArgumentException::class); + $this->expectException(InvalidArgumentException::class); NumericHelper::toOrdinal('bla-bla'); } @@ -63,7 +64,7 @@ public function testNormalize(mixed $input, string $expected): void public function testNormalizeWithIncorrectType(): void { - $this->expectException(\InvalidArgumentException::class); + $this->expectException(InvalidArgumentException::class); NumericHelper::normalize([]); } @@ -167,7 +168,38 @@ public static function dataConvertHumanReadableSizeToBytesWithInvalidStrings(): #[DataProvider('dataConvertHumanReadableSizeToBytesWithInvalidStrings')] public function testConvertHumanReadableSizeToBytesWithInvalidStrings(string $string, string $message): void { - $this->expectExceptionObject(new \InvalidArgumentException($message)); + $this->expectExceptionObject(new InvalidArgumentException($message)); NumericHelper::convertHumanReadableSizeToBytes($string); } + + public static function dataTrimDecimalZeros(): array + { + return [ + 'no decimals in integer with zeros' => ['390', '390'], + 'all zeros' => ['390.000', '390'], + 'no zeros' => ['3.14', '3.14'], + 'some zeros' => ['42.010', '42.01'], + 'zeros' => ['0.0', '0'], + 'decimal only' => ['.5', '.5'], + 'decimal zero' => ['.0', '0'], + 'start with zero' => ['0.25', '0.25'], + 'negative' => ['-3.000', '-3'], + 'null' => [null, null], + 'starts with zero' => ['02471', '02471'], + 'exponent' => ['1337e0', '1337e0'], + 'spaces' => ['3.140 ', '3.14'], + ]; + } + + #[DataProvider('dataTrimDecimalZeros')] + public function testTrimDecimalZeros(?string $input, ?string $expected): void + { + $this->assertSame($expected, NumericHelper::trimDecimalZeros($input)); + } + + public function trimDecimalZerosWithNonNumericString(): void + { + $this->expectException(InvalidArgumentException::class); + NumericHelper::trimDecimalZeros('hello'); + } } diff --git a/tests/benchmarks/NumericHelperBench.php b/tests/benchmarks/NumericHelperBench.php index 50469a8..50081bf 100644 --- a/tests/benchmarks/NumericHelperBench.php +++ b/tests/benchmarks/NumericHelperBench.php @@ -33,4 +33,16 @@ public function benchNormalize(): void NumericHelper::normalize('1,000,000.123'); NumericHelper::normalize('1 000 000,123'); } + + public function benchTrimDecimalZeros(): void + { + NumericHelper::trimDecimalZeros('390'); + NumericHelper::trimDecimalZeros('390.000'); + NumericHelper::trimDecimalZeros('3.14'); + NumericHelper::trimDecimalZeros('42.010'); + NumericHelper::trimDecimalZeros('0.0'); + NumericHelper::trimDecimalZeros('.5'); + NumericHelper::trimDecimalZeros('0.25'); + NumericHelper::trimDecimalZeros('-3.000'); + } }