Skip to content

Commit 70fe6a9

Browse files
authored
Merge pull request #13 from DouglasNeuroInformatics/dev
feat: add formatByteSize
2 parents df47913 + 069f71f commit 70fe6a9

File tree

2 files changed

+74
-1
lines changed

2 files changed

+74
-1
lines changed

src/__tests__/number.test.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, expect, it } from 'vitest';
22

3-
import { isNumberLike, parseNumber } from '../number.js';
3+
import { formatByteSize, isNumberLike, parseNumber } from '../number.js';
44

55
const NUMBER_LIKE_VALUES = [
66
5e3,
@@ -72,3 +72,54 @@ describe('parseNumber', () => {
7272
});
7373
});
7474
});
75+
76+
describe('formatByteSize', () => {
77+
it('should return bytes for values less than threshold (SI)', () => {
78+
expect(formatByteSize(999, true)).toBe('999 B');
79+
});
80+
it('should return bytes for values less than threshold (Binary)', () => {
81+
expect(formatByteSize(1023)).toBe('1023 B');
82+
});
83+
84+
it('should format 1 KB correctly (SI)', () => {
85+
expect(formatByteSize(1000, true)).toBe('1.0 KB');
86+
});
87+
88+
it('should format 1 KiB correctly (Binary)', () => {
89+
expect(formatByteSize(1024)).toBe('1.0 KiB');
90+
});
91+
92+
it('should format MB correctly with default dp (SI)', () => {
93+
expect(formatByteSize(1_500_000, true)).toBe('1.5 MB');
94+
});
95+
96+
it('should format MiB correctly with default dp (Binary)', () => {
97+
expect(formatByteSize(1_572_864)).toBe('1.5 MiB'); // 1024 * 1024 * 1.5
98+
});
99+
100+
it('should format GB correctly with custom dp (SI)', () => {
101+
expect(formatByteSize(3_000_000_000, true, 2)).toBe('3.00 GB');
102+
});
103+
104+
it('should format GiB correctly with custom dp (Binary)', () => {
105+
expect(formatByteSize(3_221_225_472, false, 3)).toBe('3.000 GiB'); // 1024^3 * 3
106+
});
107+
108+
it('should handle zero bytes', () => {
109+
expect(formatByteSize(0)).toBe('0 B');
110+
});
111+
112+
it('should handle negative values', () => {
113+
expect(formatByteSize(-1024)).toBe('-1.0 KiB');
114+
});
115+
116+
it('should not exceed the unit array length', () => {
117+
const large = 10 ** 30; // Very large number
118+
const result = formatByteSize(large, true);
119+
expect(result.endsWith('YB')).toBe(true); // YB is the last SI unit
120+
});
121+
122+
it('should round correctly with dp = 0', () => {
123+
expect(formatByteSize(1536, false, 0)).toBe('2 KiB'); // 1536 / 1024 = 1.5 → rounded to 2
124+
});
125+
});

src/number.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,25 @@ export function isNumberLike(value: unknown): value is number | string {
2020
export function parseNumber(value: unknown): number {
2121
return isNumberLike(value) ? Number(value) : NaN;
2222
}
23+
24+
export function formatByteSize(bytes: number, si = false, dp = 1): string {
25+
const thresh = si ? 1000 : 1024;
26+
27+
if (Math.abs(bytes) < thresh) {
28+
return bytes + ' B';
29+
}
30+
31+
const units = si
32+
? ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
33+
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
34+
35+
let u = -1;
36+
const r = 10 ** dp;
37+
38+
do {
39+
bytes /= thresh;
40+
++u;
41+
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
42+
43+
return bytes.toFixed(dp) + ' ' + units[u];
44+
}

0 commit comments

Comments
 (0)