Skip to content

Commit cd73d91

Browse files
committed
feat: add performance improvements
1 parent ff0547c commit cd73d91

File tree

3 files changed

+350
-0
lines changed

3 files changed

+350
-0
lines changed

src/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,15 @@ export {
7373
formatWeight,
7474
} from './specialized-formatter'
7575

76+
// Performance utilities
77+
export {
78+
bulkFormat,
79+
bulkParse,
80+
generateLargeNumbers,
81+
measureFormatPerformance,
82+
measureParsePerformance,
83+
} from './performance'
84+
7685
export type {
7786
CurrencyConfig,
7887
FormatNumberOptions,

src/performance.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import type { FormatNumberOptions, NumbersConfig, ParseNumberOptions } from './types'
2+
import { formatNumber, parseNumber } from './format'
3+
4+
/**
5+
* Bulk format an array of numbers with the same configuration
6+
* Useful for performance testing and bulk operations
7+
* @param values - Array of number values to format
8+
* @param config - Optional configuration to apply to all numbers
9+
* @returns Array of formatted strings
10+
*/
11+
export function bulkFormat(values: (number | string)[], config?: NumbersConfig): string[] {
12+
return values.map(value => formatNumber({ value, config }))
13+
}
14+
15+
/**
16+
* Bulk parse an array of string values with the same configuration
17+
* Useful for performance testing and bulk operations
18+
* @param values - Array of string values to parse
19+
* @param config - Optional configuration to apply to all parsings
20+
* @returns Array of parsed numbers
21+
*/
22+
export function bulkParse(values: string[], config?: NumbersConfig): number[] {
23+
return values.map(value => parseNumber({ value, config }))
24+
}
25+
26+
/**
27+
* Measure the performance of bulk formatting
28+
* @param values - Array of values to format
29+
* @param config - Optional configuration
30+
* @returns Performance metrics
31+
*/
32+
export function measureFormatPerformance(values: (number | string)[], config?: NumbersConfig): {
33+
totalTime: number
34+
averageTime: number
35+
operationsPerSecond: number
36+
results: string[]
37+
} {
38+
const start = performance.now()
39+
const results = bulkFormat(values, config)
40+
const end = performance.now()
41+
42+
const totalTime = end - start
43+
const averageTime = totalTime / values.length
44+
const operationsPerSecond = Math.floor(1000 / averageTime)
45+
46+
return {
47+
totalTime,
48+
averageTime,
49+
operationsPerSecond,
50+
results,
51+
}
52+
}
53+
54+
/**
55+
* Measure the performance of bulk parsing
56+
* @param values - Array of strings to parse
57+
* @param config - Optional configuration
58+
* @returns Performance metrics
59+
*/
60+
export function measureParsePerformance(values: string[], config?: NumbersConfig): {
61+
totalTime: number
62+
averageTime: number
63+
operationsPerSecond: number
64+
results: number[]
65+
} {
66+
const start = performance.now()
67+
const results = bulkParse(values, config)
68+
const end = performance.now()
69+
70+
const totalTime = end - start
71+
const averageTime = totalTime / values.length
72+
const operationsPerSecond = Math.floor(1000 / averageTime)
73+
74+
return {
75+
totalTime,
76+
averageTime,
77+
operationsPerSecond,
78+
results,
79+
}
80+
}
81+
82+
/**
83+
* Generate an array of large numbers for performance testing
84+
* @param count - Number of values to generate
85+
* @param startValue - Starting value (default: 1,000,000)
86+
* @param multiplier - Multiplier for each successive value (default: 10)
87+
* @returns Array of large numbers
88+
*/
89+
export function generateLargeNumbers(
90+
count: number,
91+
startValue = 1_000_000,
92+
multiplier = 10,
93+
): number[] {
94+
return Array.from(
95+
{ length: count },
96+
(_, i) => startValue * (multiplier ** Math.floor(i / 5)),
97+
)
98+
}

test/performance.test.ts

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
import type { NumbersConfig } from '../src/types'
2+
import { describe, expect, it } from 'bun:test'
3+
import { formatNumber, parseNumber } from '../src'
4+
import { bulkFormat, bulkParse, generateLargeNumbers, measureFormatPerformance, measureParsePerformance } from '../src/performance'
5+
6+
describe('Performance Tests', () => {
7+
describe('Large Number Formatting', () => {
8+
it('formats billion-scale numbers correctly', () => {
9+
const billions = 1_234_567_890_123
10+
const result = formatNumber({ value: billions })
11+
expect(result).toBe('1,234,567,890,123.00')
12+
})
13+
14+
it('formats trillion-scale numbers correctly', () => {
15+
const trillions = 1_234_567_890_123_456
16+
const result = formatNumber({ value: trillions })
17+
expect(result).toBe('1,234,567,890,123,456.00')
18+
})
19+
20+
it('formats quadrillion-scale numbers correctly', () => {
21+
// Using BigInt for precision, then converting to number for the API
22+
const quadrillions = Number(1_234_567_890_123_456_789n)
23+
const result = formatNumber({ value: quadrillions })
24+
// Due to JS Number precision limitations, the value is rounded
25+
expect(result).toBe('1,234,567,890,123,456,800.00')
26+
})
27+
28+
it('measures performance for formatting large numbers', () => {
29+
const testSizes = [
30+
{ name: 'Million', value: 1_234_567_890 },
31+
{ name: 'Billion', value: 1_234_567_890_123 },
32+
{ name: 'Trillion', value: 1_234_567_890_123_456 },
33+
]
34+
35+
// Using the .forEach syntax to limit linter warnings for console logs
36+
testSizes.forEach(({ name, value }) => {
37+
const start = performance.now()
38+
const result = formatNumber({ value })
39+
const end = performance.now()
40+
41+
// We're allowing these logs in tests to display performance metrics
42+
// eslint-disable-next-line no-console
43+
console.log(`Testing ${name} formatting:`)
44+
// eslint-disable-next-line no-console
45+
console.log(`- Formatted result: ${result}`)
46+
// eslint-disable-next-line no-console
47+
console.log(`- Time taken: ${(end - start).toFixed(4)}ms`)
48+
49+
// Ensure execution time is reasonable (adjust threshold as needed)
50+
expect(end - start).toBeLessThan(10) // Should format in under 10ms
51+
})
52+
})
53+
54+
it('measures performance for parsing large numbers', () => {
55+
const testSizes = [
56+
{ name: 'Million', value: '1,234,567,890.00' },
57+
{ name: 'Billion', value: '1,234,567,890,123.00' },
58+
{ name: 'Trillion', value: '1,234,567,890,123,456.00' },
59+
]
60+
61+
testSizes.forEach(({ name, value }) => {
62+
const start = performance.now()
63+
const result = parseNumber({ value })
64+
const end = performance.now()
65+
66+
// eslint-disable-next-line no-console
67+
console.log(`Testing ${name} parsing:`)
68+
// eslint-disable-next-line no-console
69+
console.log(`- Parsed result: ${result}`)
70+
// eslint-disable-next-line no-console
71+
console.log(`- Time taken: ${(end - start).toFixed(4)}ms`)
72+
73+
// Ensure execution time is reasonable
74+
expect(end - start).toBeLessThan(10) // Should parse in under 10ms
75+
})
76+
})
77+
78+
it('handles numbers with scientific notation correctly', () => {
79+
const largeNumber = 1e20
80+
const result = formatNumber({
81+
value: largeNumber,
82+
config: {
83+
useScientificNotation: false,
84+
decimalPlaces: 0,
85+
},
86+
})
87+
88+
expect(result).toBe('100,000,000,000,000,000,000')
89+
90+
const scientificResult = formatNumber({
91+
value: largeNumber,
92+
config: {
93+
useScientificNotation: true,
94+
scientificNotationThreshold: 1e10,
95+
},
96+
})
97+
98+
expect(scientificResult).toInclude('1.00e+20')
99+
})
100+
})
101+
102+
describe('Bulk Formatting Performance', () => {
103+
it('measures performance for bulk formatting operations using utility functions', () => {
104+
const count = 10000
105+
const numbers = Array.from({ length: count }, (_, i) => i * 1000 + 0.5678)
106+
107+
// Using the performance measurement utility
108+
const { totalTime, averageTime, operationsPerSecond } = measureFormatPerformance(numbers)
109+
110+
// eslint-disable-next-line no-console
111+
console.log(`Formatting ${count} numbers with utility:`)
112+
// eslint-disable-next-line no-console
113+
console.log(`- Total time: ${totalTime.toFixed(2)}ms`)
114+
// eslint-disable-next-line no-console
115+
console.log(`- Average time per number: ${averageTime.toFixed(4)}ms`)
116+
// eslint-disable-next-line no-console
117+
console.log(`- Numbers per second: ${operationsPerSecond.toLocaleString()}`)
118+
119+
// Performance assertions (adjust thresholds based on actual performance)
120+
expect(averageTime).toBeLessThan(0.1) // Average under 0.1ms per number
121+
})
122+
123+
it('measures performance for bulk parsing operations using utility functions', () => {
124+
const count = 10000
125+
const strings = Array.from({ length: count }, (_, i) => `${i * 1000 + 0.5678}`)
126+
127+
// Using the performance measurement utility
128+
const { totalTime, averageTime, operationsPerSecond } = measureParsePerformance(strings)
129+
130+
// eslint-disable-next-line no-console
131+
console.log(`Parsing ${count} numbers with utility:`)
132+
// eslint-disable-next-line no-console
133+
console.log(`- Total time: ${totalTime.toFixed(2)}ms`)
134+
// eslint-disable-next-line no-console
135+
console.log(`- Average time per number: ${averageTime.toFixed(4)}ms`)
136+
// eslint-disable-next-line no-console
137+
console.log(`- Numbers per second: ${operationsPerSecond.toLocaleString()}`)
138+
139+
// Performance assertions
140+
expect(averageTime).toBeLessThan(0.1) // Average under 0.1ms per number
141+
})
142+
143+
it('tests performance with generated large numbers', () => {
144+
// Generate an array of large numbers from 1 million to 1 trillion
145+
const largeNumbers = generateLargeNumbers(20, 1_000_000, 100)
146+
147+
// eslint-disable-next-line no-console
148+
console.log('Testing with generated large numbers:')
149+
// eslint-disable-next-line no-console
150+
console.log(`- Sample values: ${largeNumbers.slice(0, 3).join(', ')}...`)
151+
152+
const { totalTime, averageTime, operationsPerSecond } = measureFormatPerformance(largeNumbers)
153+
154+
// eslint-disable-next-line no-console
155+
console.log(`- Total time: ${totalTime.toFixed(2)}ms`)
156+
// eslint-disable-next-line no-console
157+
console.log(`- Average time per number: ${averageTime.toFixed(4)}ms`)
158+
// eslint-disable-next-line no-console
159+
console.log(`- Numbers per second: ${operationsPerSecond.toLocaleString()}`)
160+
161+
expect(averageTime).toBeLessThan(1) // Large numbers should still format under 1ms
162+
})
163+
164+
it('compares performance with different configurations', () => {
165+
const count = 1000
166+
const number = 1234567.89
167+
168+
// eslint-disable-next-line no-console
169+
console.log('Performance comparison with different configurations:')
170+
171+
// Create typed arrays of numbers
172+
const numbers = Array.from({ length: count }, () => number)
173+
174+
// Test with default config
175+
const { totalTime: defaultTime } = measureFormatPerformance(numbers)
176+
177+
// Test with scientific notation
178+
const { totalTime: scientificTime } = measureFormatPerformance(numbers, {
179+
useScientificNotation: true,
180+
})
181+
182+
// Test with digit group spacing
183+
const { totalTime: digitGroupTime } = measureFormatPerformance(numbers, {
184+
digitGroupSpacing: '2',
185+
})
186+
187+
// eslint-disable-next-line no-console
188+
console.log(`- Default config: ${defaultTime.toFixed(2)}ms for ${count} operations`)
189+
// eslint-disable-next-line no-console
190+
console.log(`- Scientific notation: ${scientificTime.toFixed(2)}ms for ${count} operations`)
191+
// eslint-disable-next-line no-console
192+
console.log(`- Custom digit grouping: ${digitGroupTime.toFixed(2)}ms for ${count} operations`)
193+
})
194+
})
195+
196+
describe('Edge Cases Performance', () => {
197+
it('handles the maximum safe integer correctly', () => {
198+
const maxSafeInt = Number.MAX_SAFE_INTEGER // 9,007,199,254,740,991
199+
const result = formatNumber({ value: maxSafeInt, config: { decimalPlaces: 0 } })
200+
expect(result).toBe('9,007,199,254,740,991')
201+
})
202+
203+
it('measures performance with special formatting requirements', () => {
204+
const count = 1000
205+
const number = 1234567.89
206+
207+
// Test with highly customized config
208+
const customConfig: NumbersConfig = {
209+
decimalPlaces: 4,
210+
decimalCharacter: ',',
211+
digitGroupSeparator: '.',
212+
currencySymbol: '€',
213+
currencySymbolPlacement: 'p',
214+
showPositiveSign: true,
215+
// Add other custom settings
216+
}
217+
218+
// Create a properly typed array of numbers
219+
const numbers = Array.from({ length: count }, () => number)
220+
const { totalTime } = measureFormatPerformance(numbers, customConfig)
221+
222+
// eslint-disable-next-line no-console
223+
console.log(`Custom config performance: ${totalTime.toFixed(2)}ms for ${count} operations`)
224+
expect(totalTime).toBeLessThan(1000) // Should complete in reasonable time
225+
})
226+
227+
it('tests bulk formatting and parsing utility functions', () => {
228+
const testNumbers = [123, 456.78, 9000, 12345.6789]
229+
230+
// Test bulkFormat
231+
const formattedNumbers = bulkFormat(testNumbers)
232+
expect(formattedNumbers).toHaveLength(testNumbers.length)
233+
expect(formattedNumbers[0]).toBe('123.00')
234+
expect(formattedNumbers[1]).toBe('456.78')
235+
236+
// Test bulkParse
237+
const parsedNumbers = bulkParse(formattedNumbers)
238+
expect(parsedNumbers).toHaveLength(formattedNumbers.length)
239+
expect(parsedNumbers[0]).toBe(123)
240+
expect(parsedNumbers[1]).toBe(456.78)
241+
})
242+
})
243+
})

0 commit comments

Comments
 (0)