Repository: tinylibs/tinybench
Pull request #233 (2025-01-28 16:26:09)

feat: improve the latency and throughput formatting in the result table
Make the result table a bit more compact, as per the discussion under issue #231

- Reduce the number of latency and throughput digits, because the least significant ones were just noise.
- Drop the ± part from the medians. They indicated the distance between the two middle elements when there was even number of them, which isn't particularly useful.
- Output the medians as numbers to get rid of the quotes.

Example result table for `simple benchmark gc`:

```
┌─────────┬───────────────┬──────────────────────┬─────────────────────┬────────────────────────────┬───────────────────────────┬─────────┐
│ (index) │ Task name     │ Latency average (ns) │ Latency median (ns) │ Throughput average (ops/s) │ Throughput median (ops/s) │ Samples │
├─────────┼───────────────┼──────────────────────┼─────────────────────┼────────────────────────────┼───────────────────────────┼─────────┤
│ 0       │ 'faster task' │ '3578.7 ± 2.83%'     │ 3125                │ '314227 ± 0.18%'           │ 320000                    │ 27944   │
│ 1       │ 'slower task' │ '1164166 ± 3.01%'    │ 1192251             │ '1321 ± 67.29%'            │ 839                       │ 86      │
└─────────┴───────────────┴──────────────────────┴─────────────────────┴────────────────────────────┴───────────────────────────┴─────────┘
```

pkg-pr-new[bot]: 
[Open in Stackblitz](https://pkg.pr.new/template/c3c3e896-c3a2-4e0f-92ce-83752f680904)


```
npm i https://pkg.pr.new/tinylibs/tinybench@233
```


_commit: <a href="https://github.com/tinylibs/tinybench/runs/36303000231"><code>775d0d1</code></a>_


Patch:
diff --git a/src/bench.ts b/src/bench.ts
index 88ef36d..50568ef 100644
--- a/src/bench.ts
+++ b/src/bench.ts
@@ -21,6 +21,7 @@ import {
 import { createBenchEvent } from './event'
 import { Task } from './task'
 import {
+  formatNumber,
   invariant,
   type JSRuntime,
   mToNs,
@@ -254,12 +255,12 @@ export class Bench extends EventTarget {
             }
           : (convert?.(task) ?? {
               'Task name': task.name,
-              'Latency average (ns)': `${mToNs(task.result.latency.mean).toFixed(2)} \xb1 ${task.result.latency.rme.toFixed(2)}%`,
+              'Latency average (ns)': `${formatNumber(mToNs(task.result.latency.mean), 5, 2)} \xb1 ${task.result.latency.rme.toFixed(2)}%`,
               // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-              'Latency median (ns)': `${mToNs(task.result.latency.p50!).toFixed(2)}${Number.parseFloat(mToNs(task.result.latency.mad!).toFixed(2)) > 0 ? ` \xb1 ${mToNs(task.result.latency.mad!).toFixed(2)}` : ''}`,
+              'Latency median (ns)': `${formatNumber(mToNs(task.result.latency.p50!), 5, 2)} \xb1 ${mToNs(task.result.latency.mad!).toFixed(2)}`,
               'Throughput average (ops/s)': `${task.result.throughput.mean.toFixed(0)} \xb1 ${task.result.throughput.rme.toFixed(2)}%`,
               // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-              'Throughput median (ops/s)': `${task.result.throughput.p50!.toFixed(0)}${Number.parseInt(task.result.throughput.mad!.toFixed(0), 10) > 0 ? ` \xb1 ${task.result.throughput.mad!.toFixed(0)}` : ''}`,
+              'Throughput median (ops/s)': `{Math.round(task.result.throughput.p50!)} \xb1 ${Math.round(task.result.throughput.mad)}`,
               Samples: task.result.latency.samples.length,
             })
         /* eslint-enable perfectionist/sort-objects */
diff --git a/src/utils.ts b/src/utils.ts
index ce9f8ad..dbc6b05 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -135,6 +135,29 @@ export const nToMs = (ns: number) => ns / 1e6
  */
 export const mToNs = (ms: number) => ms * 1e6
 
+/**
+ * @param x number to format
+ * @param targetDigits number of digits in the output to aim for
+ * @param maxFractionDigits hard limit for the number of digits after the decimal dot
+ * @returns formatted number
+ */
+export const formatNumber = (x: number, targetDigits: number, maxFractionDigits: number): string => {
+  // Round large numbers to integers, but not to multiples of 10.
+  // The actual number of significant digits may be more than `targetDigits`.
+  if (Math.abs(x) >= 10 ** targetDigits) {
+    return x.toFixed()
+  }
+
+  // Round small numbers to have `maxFractionDigits` digits after the decimal dot.
+  // The actual number of significant digits may be less than `targetDigits`.
+  if (Math.abs(x) < 10 ** (targetDigits - maxFractionDigits)) {
+    return x.toFixed(maxFractionDigits)
+  }
+
+  // Round medium magnitude numbers to have exactly `targetDigits` significant digits.
+  return x.toPrecision(targetDigits)
+}
+
 let hrtimeBigint: () => bigint
 // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
 if (typeof (globalThis as any).process?.hrtime?.bigint === 'function') {


Test patch:
diff --git a/test/utils.test.ts b/test/utils.test.ts
index 89176be..a8a1c0c 100644
--- a/test/utils.test.ts
+++ b/test/utils.test.ts
@@ -1,6 +1,28 @@
 import { expect, test } from 'vitest'
 
 import { getStatisticsSorted } from '../src/utils'
+import { formatNumber } from '../src/utils'
+
+
+test('formatting integers', () => {
+  expect(formatNumber(123456, 5, 2)).toBe('123456')
+  expect(formatNumber(12345, 5, 2)).toBe('12345')
+  expect(formatNumber(1234, 5, 2)).toBe('1234.0')
+  expect(formatNumber(123, 5, 2)).toBe('123.00')
+  expect(formatNumber(12, 5, 2)).toBe('12.00')
+  expect(formatNumber(1, 5, 2)).toBe('1.00')
+  expect(formatNumber(0, 5, 2)).toBe('0.00')
+  expect(formatNumber(-1, 5, 2)).toBe('-1.00')
+})
+
+test('formatting floats', () => {
+  expect(formatNumber(123456.789, 5, 2)).toBe('123457')
+  expect(formatNumber(12345.6789, 5, 2)).toBe('12346')
+  expect(formatNumber(1234.56789, 5, 2)).toBe('1234.6')
+  expect(formatNumber(123.456789, 5, 2)).toBe('123.46')
+  expect(formatNumber(12.3456789, 5, 2)).toBe('12.35')
+  expect(formatNumber(-12.3456789, 5, 2)).toBe('-12.35')    
+})  
 
 test('median absolute deviation', () => {
   // https://www.wolframalpha.com/input?i=MedianDeviation[1,2,3]