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
52 changes: 52 additions & 0 deletions .changeset/late-wings-add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
"vn-number": major
---

## Performance Optimization & Code Quality Improvements

### Breaking Changes

- **Refactored from OOP to functional programming**: Removed class-based architecture (`NumberReader`, `Numbers`, `Thousand`, `Million`, `Billion`, `Zerofill` classes) in favor of pure functional approach
- Internal implementation completely restructured for better performance and maintainability
- **Note**: Public API (`readVnNumber`) remains unchanged and fully compatible

### Performance Improvements

- **Eliminated class instantiation overhead**: No more object creation for each 3-digit group
- **Removed property access overhead**: Direct string index access instead of class properties
- **Eliminated inheritance chain lookups**: Flat function calls instead of prototype chain traversal
- **Removed dynamic dispatch**: Direct function calls instead of class lookup maps
- **Optimized string operations**: More efficient concatenation patterns
- **Benchmarks**: Maintained excellent performance (2.3-6.8M ops/sec for typical use cases)

### Code Quality

- **Reduced cognitive complexity**: Refactored complex functions to meet SonarCloud standards (<15 complexity)
- `readThreeDigits`: Complexity reduced from 27 to <15
- `readVnNumber`: Complexity reduced from 37 to <15
- **Better code organization**: Split monolithic file into 5 focused modules:
- `digits.ts` - Digit mapping and conversion
- `utils.ts` - Utility functions for grouping and validation
- `three-digits.ts` - Three-digit group reading logic
- `groups.ts` - Group type calculation and processing
- `index.ts` - Clean public API entry point
- **Improved maintainability**: Smaller, focused functions with single responsibilities
- **Better testability**: Individual modules can be tested in isolation

### Testing

- **Expanded test coverage**: Increased from 42 to 68 tests (+62% increase)
- **Comprehensive edge case coverage**: Added extensive tests for:
- All leading zero patterns (001, 068, 060, etc.)
- All tens patterns (10-99) with special rules
- All hundreds patterns (100-999)
- Mixed magnitude numbers
- Boundary values at each magnitude level
- All Vietnamese reading rules (mốt, lăm, lẻ, không trăm)
- **91 total tests passing** (68 for read module, 23 for format module)

### Internal Changes

- Removed 12 files (old class implementations and their tests)
- Added 4 new modular files with clear separation of concerns
- Maintained 100% backward compatibility for public API
5 changes: 5 additions & 0 deletions .changeset/wide-things-dig.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"vn-number": patch
---

Optimize logic of `formatNumber` function
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

🛠 A bunch of utility functions that work with number in 🇻🇳 Vietnamese language

[![Publish](https://github.com/hckhanh/vn-number/actions/workflows/publish.yml/badge.svg)](https://github.com/hckhanh/vn-number/actions/workflows/publish.yml)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=hckhanh_vn-number&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=hckhanh_vn-number)
[![CodSpeed Badge](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/hckhanh/vn-number?utm_source=badge)
[![NPM Downloads](https://img.shields.io/npm/dw/vn-number)](https://www.npmjs.com/package/vn-number)
[![JSR](https://jsr.io/badges/@hckhanh/vn-number/weekly-downloads)](https://jsr.io/@hckhanh/vn-number)

Expand Down
19 changes: 19 additions & 0 deletions src/format/number.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ describe('formatVnNumber', () => {
expect(formatVnNumber(10000000)).to.eq('10.000.000')
})

it('format bigint value', () => {
expect(formatVnNumber(BigInt(10000000))).to.eq('10.000.000')
expect(formatVnNumber(BigInt('9999999999999999'))).to.eq(
'9.999.999.999.999.999',
)
})

it('format invalid number by fallback value', () => {
expect(formatVnNumber('100,0,0.000')).to.eq('0')
})
Expand Down Expand Up @@ -36,6 +43,13 @@ describe('formatVnCurrency', () => {
expect(formatVnCurrency(10000000)).to.match(/10\.000\.000\s₫/)
})

it('format bigint value in VND', () => {
expect(formatVnCurrency(BigInt(10000000))).to.match(/10\.000\.000\s₫/)
expect(formatVnCurrency(BigInt('9999999999999999'))).to.match(
/9\.999\.999\.999\.999\.999\s₫/,
)
})

it('format invalid number by fallback value', () => {
expect(formatVnCurrency('100,0,0.000')).to.eq('0\u00A0₫')
})
Expand Down Expand Up @@ -73,6 +87,11 @@ describe('formatVnPercent', () => {
expect(formatVnPercent(0.1)).to.eq('10%')
})

it('format bigint value as percent', () => {
expect(formatVnPercent(BigInt(1))).to.eq('100%')
expect(formatVnPercent(BigInt(5))).to.eq('500%')
})

it('format invalid number by fallback value', () => {
expect(formatVnPercent('100,0,0.000')).to.eq('0%')
})
Expand Down
22 changes: 7 additions & 15 deletions src/format/number.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,13 @@ function formatNumber(
formatter: Intl.NumberFormat,
fallbackValue: string,
) {
if (
(typeof number === 'number' && !isNaN(number)) ||
typeof number === 'bigint'
) {
return formatter.format(number)
} else if (typeof number === 'string') {
try {
const num = Number(number)
return isNaN(num) ? fallbackValue : formatter.format(num)
} catch {
return fallbackValue
}
} else {
return fallbackValue
}
if (number == null) return fallbackValue
if (typeof number === 'bigint') return formatter.format(number)
if (typeof number === 'number')
return Number.isNaN(number) ? fallbackValue : formatter.format(number)

const num = Number(number)
return Number.isNaN(num) ? fallbackValue : formatter.format(num)
}

const VN_NUMBER_FORMATTER = new Intl.NumberFormat('vi-VN')
Expand Down
24 changes: 0 additions & 24 deletions src/read/Billion.test.ts

This file was deleted.

10 changes: 0 additions & 10 deletions src/read/Billion.ts

This file was deleted.

19 changes: 0 additions & 19 deletions src/read/Million.test.ts

This file was deleted.

10 changes: 0 additions & 10 deletions src/read/Million.ts

This file was deleted.

154 changes: 0 additions & 154 deletions src/read/Number.test.ts

This file was deleted.

Loading