Skip to content

Commit 6d8767a

Browse files
committed
feat: added formatNumber util
1 parent 803136d commit 6d8767a

File tree

4 files changed

+221
-0
lines changed

4 files changed

+221
-0
lines changed

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,43 @@ extractYouTubeVideoID("https://www.youtube.com/watch?v=JWJz_MS1-I8&t=1815s"); //
6767
extractYouTubeVideoID("https://youtu.be/JWJz_MS1-I8"); // JWJz_MS1-I8
6868
```
6969

70+
### `formatNumber`
71+
72+
Formats numbers into human-readable strings with appropriate unit suffixes (k, m, b).
73+
74+
**Usage:**
75+
76+
```typescript
77+
import { formatNumber } from "largs-utils";
78+
79+
// Thousands
80+
formatNumber(1000); // "1k"
81+
formatNumber(1500); // "1.5k"
82+
formatNumber(45678); // "45.7k"
83+
84+
// Millions
85+
formatNumber(1000000); // "1m"
86+
formatNumber(2500000); // "2.5m"
87+
formatNumber(1234567); // "1.2m"
88+
89+
// Billions
90+
formatNumber(1000000000); // "1b"
91+
formatNumber(5500000000); // "5.5b"
92+
93+
// Regular numbers (less than 1000)
94+
formatNumber(999); // "999"
95+
formatNumber(123); // "123"
96+
97+
// Custom decimal precision
98+
formatNumber(1234, 0); // "1k"
99+
formatNumber(1234, 1); // "1.2k"
100+
formatNumber(1234, 2); // "1.23k"
101+
102+
// Negative numbers
103+
formatNumber(-1500); // "-1.5k"
104+
formatNumber(-2500000); // "-2.5m"
105+
```
106+
70107
### `generatePrefixedId`
71108

72109
Generates UUID with a prefix

src/__tests__/formatNumber.test.ts

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import { formatNumber } from "../formatNumber";
2+
3+
describe("formatNumber", () => {
4+
describe("billions formatting", () => {
5+
it("should format billions with 'b' suffix", () => {
6+
expect(formatNumber(1000000000)).toBe("1b");
7+
expect(formatNumber(1500000000)).toBe("1.5b");
8+
expect(formatNumber(2300000000)).toBe("2.3b");
9+
});
10+
11+
it("should format large billions correctly", () => {
12+
expect(formatNumber(15000000000)).toBe("15b");
13+
expect(formatNumber(999999999999)).toBe("1000b");
14+
});
15+
});
16+
17+
describe("millions formatting", () => {
18+
it("should format millions with 'm' suffix", () => {
19+
expect(formatNumber(1000000)).toBe("1m");
20+
expect(formatNumber(1500000)).toBe("1.5m");
21+
expect(formatNumber(2300000)).toBe("2.3m");
22+
});
23+
24+
it("should format large millions correctly", () => {
25+
expect(formatNumber(999000000)).toBe("999m");
26+
expect(formatNumber(500000000)).toBe("500m");
27+
});
28+
});
29+
30+
describe("thousands formatting", () => {
31+
it("should format thousands with 'k' suffix", () => {
32+
expect(formatNumber(1000)).toBe("1k");
33+
expect(formatNumber(1500)).toBe("1.5k");
34+
expect(formatNumber(2300)).toBe("2.3k");
35+
});
36+
37+
it("should format large thousands correctly", () => {
38+
expect(formatNumber(999000)).toBe("999k");
39+
expect(formatNumber(500000)).toBe("500k");
40+
});
41+
});
42+
43+
describe("regular numbers", () => {
44+
it("should format numbers less than 1000 without suffix", () => {
45+
expect(formatNumber(0)).toBe("0");
46+
expect(formatNumber(1)).toBe("1");
47+
expect(formatNumber(100)).toBe("100");
48+
expect(formatNumber(999)).toBe("999");
49+
});
50+
51+
it("should handle decimal numbers less than 1000", () => {
52+
expect(formatNumber(1.5)).toBe("1.5");
53+
expect(formatNumber(99.9)).toBe("99.9");
54+
expect(formatNumber(123.456)).toBe("123.5");
55+
});
56+
});
57+
58+
describe("decimal precision", () => {
59+
it("should respect custom digits parameter", () => {
60+
expect(formatNumber(1234, 0)).toBe("1k");
61+
expect(formatNumber(1234, 1)).toBe("1.2k");
62+
expect(formatNumber(1234, 2)).toBe("1.23k");
63+
expect(formatNumber(1234, 3)).toBe("1.234k");
64+
});
65+
66+
it("should handle precision for millions", () => {
67+
expect(formatNumber(1234567, 0)).toBe("1m");
68+
expect(formatNumber(1234567, 1)).toBe("1.2m");
69+
expect(formatNumber(1234567, 2)).toBe("1.23m");
70+
});
71+
72+
it("should handle precision for billions", () => {
73+
expect(formatNumber(1234567890, 0)).toBe("1b");
74+
expect(formatNumber(1234567890, 1)).toBe("1.2b");
75+
expect(formatNumber(1234567890, 2)).toBe("1.23b");
76+
});
77+
});
78+
79+
describe("trailing zeros cleanup", () => {
80+
it("should remove trailing zeros after decimal", () => {
81+
expect(formatNumber(1000)).toBe("1k");
82+
expect(formatNumber(2000000)).toBe("2m");
83+
expect(formatNumber(3000000000)).toBe("3b");
84+
});
85+
86+
it("should keep significant decimals and remove trailing zeros", () => {
87+
expect(formatNumber(1100, 2)).toBe("1.1k");
88+
expect(formatNumber(1010, 2)).toBe("1.01k");
89+
expect(formatNumber(1001, 3)).toBe("1.001k");
90+
});
91+
});
92+
93+
describe("negative numbers", () => {
94+
it("should handle negative thousands", () => {
95+
expect(formatNumber(-1000)).toBe("-1k");
96+
expect(formatNumber(-1500)).toBe("-1.5k");
97+
});
98+
99+
it("should handle negative millions", () => {
100+
expect(formatNumber(-1000000)).toBe("-1m");
101+
expect(formatNumber(-2500000)).toBe("-2.5m");
102+
});
103+
104+
it("should handle negative billions", () => {
105+
expect(formatNumber(-1000000000)).toBe("-1b");
106+
expect(formatNumber(-5500000000)).toBe("-5.5b");
107+
});
108+
109+
it("should handle negative numbers less than 1000", () => {
110+
expect(formatNumber(-1)).toBe("-1");
111+
expect(formatNumber(-100)).toBe("-100");
112+
expect(formatNumber(-999)).toBe("-999");
113+
});
114+
});
115+
116+
describe("edge cases", () => {
117+
it("should handle zero", () => {
118+
expect(formatNumber(0)).toBe("0");
119+
expect(formatNumber(-0)).toBe("0");
120+
});
121+
122+
it("should handle very small numbers", () => {
123+
expect(formatNumber(0.1)).toBe("0.1");
124+
expect(formatNumber(0.01)).toBe("0");
125+
});
126+
127+
it("should handle numbers just below thresholds", () => {
128+
expect(formatNumber(999)).toBe("999");
129+
expect(formatNumber(999999)).toBe("1000k");
130+
expect(formatNumber(999999999)).toBe("1000m");
131+
});
132+
});
133+
134+
describe("invalid inputs", () => {
135+
it("should return '0' for NaN", () => {
136+
expect(formatNumber(NaN)).toBe("0");
137+
});
138+
139+
it("should return '0' for non-number types", () => {
140+
expect(formatNumber("1000" as any)).toBe("0");
141+
expect(formatNumber(null as any)).toBe("0");
142+
expect(formatNumber(undefined as any)).toBe("0");
143+
expect(formatNumber({} as any)).toBe("0");
144+
expect(formatNumber([] as any)).toBe("0");
145+
});
146+
});
147+
148+
describe("real-world examples", () => {
149+
it("should format social media counts correctly", () => {
150+
expect(formatNumber(1234)).toBe("1.2k"); // 1.2k followers
151+
expect(formatNumber(45678)).toBe("45.7k"); // 45.7k likes
152+
expect(formatNumber(1234567)).toBe("1.2m"); // 1.2m views
153+
expect(formatNumber(9876543210)).toBe("9.9b"); // 9.9b population
154+
});
155+
156+
it("should format file sizes", () => {
157+
expect(formatNumber(1024, 0)).toBe("1k");
158+
expect(formatNumber(1536, 1)).toBe("1.5k");
159+
expect(formatNumber(1048576, 0)).toBe("1m");
160+
});
161+
});
162+
});
163+

src/formatNumber.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export function formatNumber(num: number, digits = 1): string {
2+
if (typeof num !== 'number' || isNaN(num)) return '0';
3+
4+
const units = [
5+
{ value: 1e9, symbol: 'b' },
6+
{ value: 1e6, symbol: 'm' },
7+
{ value: 1e3, symbol: 'k' },
8+
{ value: 1, symbol: '' },
9+
];
10+
11+
const isNegative = num < 0;
12+
const absNum = Math.abs(num);
13+
14+
const unit = units.find(u => absNum >= u.value) || units[units.length - 1];
15+
const formatted = (absNum / unit.value).toFixed(digits);
16+
17+
const clean = formatted.replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1');
18+
19+
return `${isNegative ? '-' : ''}${clean}${unit.symbol}`;
20+
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export { isYoutubeUrl } from "./isYoutubeUrl";
1414
export { isValidEmail } from "./isValidEmail";
1515
export { isValidGoogleMapsUrl } from "./isValidGoogleMapsUrl";
1616
export { isValidHttpUrl } from "./isValidHttpUrl";
17+
export { formatNumber } from "./formatNumber";
1718
export { shuffleArray } from "./shuffleArray";
1819
export { toKebabCase } from "./toKebabCase";
1920
export { unslugify } from "./unslugify";

0 commit comments

Comments
 (0)