Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ const count = stringzy.analyze.wordCount('Hello world'); // 2
- [trim](#trim) - Removes unnecessary whitespace from a string.
- [formatRomanNumeral](#formatromannumeral) - Converts a positive integer into its Roman numeral representation.
- [formatPercentage](#formatpercentage) - Converts a number into a percentage string with configurable decimal precision.
- [formatFileSize](#formatfilesize) - Converts a number of bytes into a human-readable file size string (B, KB, MB, GB, TB).

## 📋 API Reference

Expand Down Expand Up @@ -1352,6 +1353,32 @@ formatPercentage(0.5, -1); // TypeError
| num | number | required | The number to convert into a percentage string. |
| precision | number | 2 | The number of decimal places to include in the output. |

#### <a id="formatfilesize"></a>formatFileSize(bytes, precision)
Converts a number of bytes into a human-readable file size string (B, KB, MB, GB, TB). </br>
Automatically scales the unit based on the size and supports configurable decimal precision.</br>
Throws an error for invalid, non-numeric, or negative inputs.</br>

```javascript
import { formatFileSize } from 'stringzy';

formatFileSize(123); // "123 B"
formatFileSize(1024); // "1 KB"
formatFileSize(1048576); // "1 MB"
formatFileSize(1073741824); // "1 GB"
formatFileSize(1572864); // "1.5 MB"
formatFileSize(1500, 3); // "1.465 KB"

// Invalid cases
formatFileSize(-1024); // RangeError
formatFileSize('1024'); // TypeError
formatFileSize(1024, -1); // TypeError
```

| Parameter | Type | Default | Description |
| --------- | ------ | -------- | --------------------------------------------------------------- |
| bytes | number | required | The number of bytes to convert into a human-readable file size. |
| precision | number | 2 | The number of decimal places for fractional sizes. |

## 🔧 Usage Patterns

### Individual Function Imports
Expand Down
42 changes: 42 additions & 0 deletions src/formatting/fileSize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Converts a number of bytes into a human-readable file size string (B, KB, MB, GB, TB).
*
* Supports values from bytes up to terabytes, with automatic unit scaling and correct rounding.
*
* Examples:
* 123 → "123 B"
* 1024 → "1 KB"
* 1048576 → "1 MB"
* 1073741824 → "1 GB"
* 1572864 → "1.5 MB"
*
* @param {number} bytes - The number of bytes to convert.
* @param {number} [precision=2] - The number of decimal places to include for non-integer conversions.
* @returns {string} The formatted file size string with units.
* @throws {TypeError} If the input is not a number or precision is not a valid number.
*/
export function formatFileSize(bytes: number, precision: number = 2): string {
if (typeof bytes !== 'number' || Number.isNaN(bytes)) {
throw new TypeError('Input must be a number');
}

if (typeof precision !== 'number' || Number.isNaN(precision) || precision < 0) {
throw new TypeError('Precision must be a non-negative number');
}

if (bytes < 0) {
throw new RangeError('File size cannot be negative');
}

const units = ['B', 'KB', 'MB', 'GB', 'TB'];
if (bytes === 0) return `0 B`;

const i = Math.floor(Math.log(bytes) / Math.log(1024));
const value = bytes / Math.pow(1024, i);

// ✅ Fix: trim trailing zeros by converting to number before string
const rounded =
value % 1 === 0 ? value.toString() : parseFloat(value.toFixed(precision)).toString();

return `${rounded} ${units[i]}`;
}
5 changes: 4 additions & 1 deletion src/formatting/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export { formatDuration } from './duration';
export { trim } from './trim';
export { formatRomanNumeral } from './romanNumerals';
export { formatPercentage } from './percentage';
export { formatFileSize } from './fileSize';

import { capitalize } from './capitalize';
import { formatNumber } from './number';
Expand All @@ -13,6 +14,7 @@ import { formatDuration } from './duration';
import { trim } from './trim';
import { formatRomanNumeral } from './romanNumerals';
import { formatPercentage } from './percentage';
import { formatFileSize } from './fileSize';

export const formatting = {
capitalize,
Expand All @@ -21,5 +23,6 @@ export const formatting = {
formatDuration,
trim,
formatRomanNumeral,
formatPercentage
formatPercentage,
formatFileSize
};
41 changes: 41 additions & 0 deletions src/tests/formatting/fileSize.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { describe, it } from 'node:test';
import assert from 'node:assert';
import { formatFileSize } from '../../formatting/fileSize';

describe('formatFileSize', () => {
it('formats basic sizes correctly', () => {
assert.strictEqual(formatFileSize(123), '123 B');
assert.strictEqual(formatFileSize(1024), '1 KB');
assert.strictEqual(formatFileSize(1048576), '1 MB');
assert.strictEqual(formatFileSize(1073741824), '1 GB');
});

it('handles fractional sizes with correct rounding', () => {
assert.strictEqual(formatFileSize(1572864), '1.5 MB'); // 1.5 MB
assert.strictEqual(formatFileSize(1536, 1), '1.5 KB'); // 1.5 KB
});

it('handles precision configuration', () => {
assert.strictEqual(formatFileSize(1500, 0), '1 KB'); // rounds down
assert.strictEqual(formatFileSize(1500, 3), '1.465 KB');
});

it('handles zero correctly', () => {
assert.strictEqual(formatFileSize(0), '0 B');
});

it('throws TypeError for invalid inputs', () => {
assert.throws(() => formatFileSize('1024' as any), /Input must be a number/);
assert.throws(() => formatFileSize(null as any), /Input must be a number/);
assert.throws(() => formatFileSize(undefined as any), /Input must be a number/);
});

it('throws TypeError for invalid precision', () => {
assert.throws(() => formatFileSize(1024, -1), /Precision must be a non-negative number/);
assert.throws(() => formatFileSize(1024, '2' as any), /Precision must be a non-negative number/);
});

it('throws RangeError for negative byte values', () => {
assert.throws(() => formatFileSize(-1024), /File size cannot be negative/);
});
});