my website at ewancroft.uk
1/**
2 * Number formatting utilities
3 */
4
5/**
6 * Determines the effective locale, preferring system locale with fallback to 'en-GB'.
7 */
8function getLocale(locale?: string): string {
9 return locale || (typeof navigator !== 'undefined' && navigator.language) || 'en-GB';
10}
11
12/**
13 * Formats large numbers into a compact, human-readable format.
14 * Automatically adapts to the given or system locale.
15 * Numbers are rounded DOWN to ensure stats don't appear inflated.
16 * @param num - The number to format
17 * @param locale - Optional locale string (defaults to system or 'en-GB')
18 * @returns Formatted string (e.g., "1.2K", "3.4M")
19 */
20export function formatCompactNumber(num?: number, locale?: string): string {
21 if (num === undefined || num === null) return '0';
22 const effectiveLocale = getLocale(locale);
23
24 // For numbers >= 1000, round down to one decimal place for the compact format
25 if (num >= 1000) {
26 // Determine the divisor (1000 for K, 1000000 for M, etc.)
27 const divisor = num >= 1000000000 ? 1000000000 : num >= 1000000 ? 1000000 : 1000;
28 // Floor to one decimal place: floor(num / divisor * 10) / 10
29 const roundedDown = Math.floor((num / divisor) * 10) / 10;
30 // Re-multiply to get the actual number to format
31 const adjustedNum = roundedDown * divisor;
32
33 return new Intl.NumberFormat(effectiveLocale, {
34 notation: 'compact',
35 compactDisplay: 'short',
36 maximumFractionDigits: 1
37 }).format(adjustedNum);
38 }
39
40 // For numbers < 1000, just return as-is
41 return new Intl.NumberFormat(effectiveLocale, {
42 notation: 'compact',
43 compactDisplay: 'short',
44 maximumFractionDigits: 1
45 }).format(num);
46}
47
48/**
49 * Formats a number with thousand separators.
50 * Automatically adapts to the given or system locale.
51 * @param num - The number to format
52 * @param locale - Optional locale string (defaults to system or 'en-GB')
53 * @returns Formatted string (e.g., "1,234,567")
54 */
55export function formatNumber(num: number, locale?: string): string {
56 const effectiveLocale = getLocale(locale);
57 return new Intl.NumberFormat(effectiveLocale).format(num);
58}