Repository: phpbench/phpbench (php)

Issue:
Option/default to strip tailing zeros when displaying numbers.K
For example:

```
1,000.000μs
```

requires more effort to parse than:

```
1,000μs
```

We should strip "padding" zeros from the end of numbers.

Pull request description:
Option to strip trailing zeros
Fixes #958 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b9ca288b..7c4f876d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,7 +6,8 @@ master
 
 Improvements
 
-- Show more concise error messages including script exit code
+- Show more concise error messages including script exit code #969
+- Configuration (`expression.strip_tailing_zeros`) to strip meaningless zeros after the decimal place #958
 
 1.2.3
 ------
diff --git a/docs/configuration.rst b/docs/configuration.rst
index 2a701532..6d4f0f37 100644
--- a/docs/configuration.rst
+++ b/docs/configuration.rst
@@ -544,6 +544,17 @@ Types: ``["string"]``
 
 Allowed values: ``["binary","decimal"]``
 
+.. _configuration_expression_strip_tailing_zeros:
+
+expression.strip_tailing_zeros
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Do not display meaningless zeros after the decimal place
+
+Default: ``false``
+
+Types: ``["bool"]``
+
 Storage
 -------
 
diff --git a/lib/Expression/NodePrinter/DisplayAsPrinter.php b/lib/Expression/NodePrinter/DisplayAsPrinter.php
index e08a1c52..df5b7bc3 100644
--- a/lib/Expression/NodePrinter/DisplayAsPrinter.php
+++ b/lib/Expression/NodePrinter/DisplayAsPrinter.php
@@ -14,6 +14,7 @@ use PhpBench\Expression\Exception\PrinterError;
 use PhpBench\Expression\NodePrinter;
 use PhpBench\Expression\Printer;
 use PhpBench\Util\MemoryUnit;
+use PhpBench\Util\NumberFormat;
 use PhpBench\Util\TimeUnit;
 
 class DisplayAsPrinter implements NodePrinter
@@ -34,10 +35,16 @@ class DisplayAsPrinter implements NodePrinter
      */
     private $memoryAsBinary;
 
-    public function __construct(TimeUnit $timeUnit, bool $memoryAsBinary)
+    /**
+     * @var bool
+     */
+    private $stripTailingZeros;
+
+    public function __construct(TimeUnit $timeUnit, bool $memoryAsBinary, bool $stripTailingZeros = false)
     {
         $this->timeUnit = $timeUnit;
         $this->memoryAsBinary = $memoryAsBinary;
+        $this->stripTailingZeros = $stripTailingZeros;
     }
 
     public function print(Printer $printer, Node $node): ?string
@@ -115,13 +122,14 @@ class DisplayAsPrinter implements NodePrinter
 
     private function timeUnit(float $value, ?string $unit, ?int $precision, string $prettyUnit, ?string $mode): string
     {
-        return sprintf('%s%s', number_format(
+        return sprintf('%s%s', NumberFormat::format(
             $this->timeUnit->toDestUnit(
                 $value,
                 $this->timeUnit->resolveDestUnit($unit, $value),
                 $mode
             ),
-            $precision ?: $this->timeUnit->getPrecision()
+            $precision ?: $this->timeUnit->getPrecision(),
+            $this->stripTailingZeros,
         ), $prettyUnit);
     }
 
@@ -132,7 +140,11 @@ class DisplayAsPrinter implements NodePrinter
     {
         return sprintf(
             '%s%s',
-            number_format(MemoryUnit::convertTo($value, MemoryUnit::BYTES, $unit), $unit === MemoryUnit::BYTES ? 0 : ($precision ?: 3)),
+            NumberFormat::format(
+                MemoryUnit::convertTo($value, MemoryUnit::BYTES, $unit),
+                $unit === MemoryUnit::BYTES ? 0 : ($precision ?: 3),
+                $this->stripTailingZeros
+            ),
             $prettyUnit
         );
     }
diff --git a/lib/Extension/ExpressionExtension.php b/lib/Extension/ExpressionExtension.php
index 4d818986..5b58ec6f 100644
--- a/lib/Extension/ExpressionExtension.php
+++ b/lib/Extension/ExpressionExtension.php
@@ -130,6 +130,7 @@ class ExpressionExtension implements ExtensionInterface
     public const PARAM_SYNTAX_HIGHLIGHTING = 'expression.syntax_highlighting';
     public const PARAM_THEME = 'expression.theme';
     public const PARAM_MEMORY_UNIT_PREFIX = 'expression.memory_unit_prefix';
+    public const PARAM_STRIP_TAILING_ZEROS = 'expression.strip_tailing_zeros';
 
     public const SERVICE_PLAIN_PRINTER = 'expression.printer.plain';
     public const SERVICE_BARE_PRINTER = 'expression.printer.bare';
@@ -296,7 +297,8 @@ class ExpressionExtension implements ExtensionInterface
                 new ValueWithUnitPrinter(),
                 new DisplayAsPrinter(
                     $container->get(TimeUnit::class),
-                    $container->getParameter(self::PARAM_MEMORY_UNIT_PREFIX) === self::PREFIX_BINARY
+                    $container->getParameter(self::PARAM_MEMORY_UNIT_PREFIX) === self::PREFIX_BINARY,
+                    $container->getParameter(self::PARAM_STRIP_TAILING_ZEROS)
                 ),
                 new ParameterPrinter(),
                 new StringPrinter(),
@@ -390,14 +392,18 @@ class ExpressionExtension implements ExtensionInterface
             self::PARAM_SYNTAX_HIGHLIGHTING => true,
             self::PARAM_THEME => self::THEME_SOLARIZED,
             self::PARAM_MEMORY_UNIT_PREFIX => self::PREFIX_DECIMAL,
+            self::PARAM_STRIP_TAILING_ZEROS => false,
         ]);
         $resolver->setAllowedTypes(self::PARAM_SYNTAX_HIGHLIGHTING, 'bool');
         $resolver->setAllowedTypes(self::PARAM_THEME, 'string');
         $resolver->setAllowedTypes(self::PARAM_MEMORY_UNIT_PREFIX, ['string']);
+        $resolver->setAllowedTypes(self::PARAM_STRIP_TAILING_ZEROS, ['bool']);
         $resolver->setAllowedValues(self::PARAM_MEMORY_UNIT_PREFIX, [self::PREFIX_BINARY, self::PREFIX_DECIMAL]);
+
         SymfonyOptionsResolverCompat::setInfos($resolver, [
             self::PARAM_SYNTAX_HIGHLIGHTING => 'Enable syntax highlighting',
             self::PARAM_THEME => 'Select a theme to use',
+            self::PARAM_STRIP_TAILING_ZEROS => 'Do not display meaningless zeros after the decimal place',
             self::PARAM_MEMORY_UNIT_PREFIX => sprintf(
                 'By default use ``%s`` (1kb = 1000 bytes) or ``%s`` (1KiB = 1024 bytes) when displaying memory',
                 self::PREFIX_DECIMAL,
diff --git a/lib/Util/NumberFormat.php b/lib/Util/NumberFormat.php
new file mode 100644
index 00000000..ed8c0e9e
--- /dev/null
+++ b/lib/Util/NumberFormat.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace PhpBench\Util;
+
+use function number_format;
+
+class NumberFormat
+{
+    /**
+     * Similar to the built-in number_format but meaningless zeros after the
+     * decimal place are trimmed.
+     */
+    public static function format(float $number, int $decimals, bool $stripTailingZeros): string
+    {
+        $formated = number_format($number, $decimals);
+
+        if ($stripTailingZeros && false !== strpos($formated, '.')) {
+            $formated = rtrim($formated, '0');
+
+            if (substr($formated, -1) === '.') {
+                return (string)$number;
+            }
+        }
+
+        return $formated;
+    }
+}
diff --git a/phpbench.json b/phpbench.json
index 05708bca..3812e012 100644
--- a/phpbench.json
+++ b/phpbench.json
@@ -15,6 +15,7 @@
     "runner.retry_threshold": 5,
     "runner.revs": 10,
     "runner.assert": "mode(variant.time.avg) as ms <= mode(baseline.time.avg) as ms +/- 5%",
+    "expression.strip_tailing_zeros": true,
     "report.generators": {
         "examples": {
             "generator": "composite",
diff --git a/phpbench.schema.json b/phpbench.schema.json
index 14cb3232..5fb5c39b 100644
--- a/phpbench.schema.json
+++ b/phpbench.schema.json
@@ -317,6 +317,12 @@
                 "decimal"
             ]
         },
+        "expression.strip_tailing_zeros": {
+            "description": "Do not display meaningless zeros after the decimal place",
+            "type": [
+                "boolean"
+            ]
+        },
         "storage.driver": {
             "description": "Storage driver to use",
             "type": [


Test patch:
diff --git a/tests/Unit/Util/NumberFormatTest.php b/tests/Unit/Util/NumberFormatTest.php
new file mode 100644
index 00000000..1f70dde1
--- /dev/null
+++ b/tests/Unit/Util/NumberFormatTest.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace PhpBench\Tests\Unit\Util;
+
+use Generator;
+use PHPUnit\Framework\TestCase;
+use PhpBench\Util\NumberFormat;
+
+class NumberFormatTest extends TestCase
+{
+    /**
+     * @dataProvider provideFormat
+     */
+    public function testFormat(float $number, int $decimals, bool $stripTrailingZeros, string $expected): void
+    {
+        self::assertEquals($expected, NumberFormat::format($number, $decimals, $stripTrailingZeros));
+    }
+
+    /**
+     * @return Generator<mixed>
+     */
+    public function provideFormat(): Generator
+    {
+        yield [0, 0, true, '0'];
+
+        yield [0, 1, true, '0'];
+
+        yield [0, 2, true, '0'];
+
+        yield [10.12, 2, true, '10.12'];
+
+        yield [10.12, 3, true, '10.12'];
+
+        yield [10.000, 3, true, '10'];
+
+        yield [0, 0, false, '0'];
+
+        yield [0, 1, false, '0.0'];
+
+        yield [0, 2, false, '0.00'];
+
+        yield [10.12, 2, false, '10.12'];
+
+        yield [10.12, 3, false, '10.120'];
+
+        yield [10.000, 3, false, '10.000'];
+    }
+}