npm package: @norbulcz/num-parse
Description: Strict, locale-tolerant number parser (US/EU/CH) with validated grouping & currency stripping. Zero deps.
Author: Norbert Lorincz
Latest version: 0.1.0

# @norbulcz/num-parse

> Strict, locale-tolerant number parser (US/EU/CH) with validated grouping. Zero dependencies.

[![npm version](https://badge.fury.io/js/@norbulcz%2Fnum-parse.svg)](https://badge.fury.io/js/@norbulcz%2Fnum-parse)
[![Performance](https://img.shields.io/badge/performance-4.4M%2B%20ops%2Fsec-brightgreen)](https://github.com/norbulcz/num-parse)
[![Bundle Size](https://img.shields.io/badge/bundle%20size-1.2KB%20gzipped-blue)](https://bundlephobia.com/package/@norbulcz/num-parse)
[![Zero Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)](https://github.com/norbulcz/num-parse)

A robust TypeScript library for parsing numbers from strings with support for multiple international formats including US, European, and Swiss number formatting conventions.

## Features

- 🌍 **Multi-locale support**: US, EU, and Swiss number formats
- 🔒 **Strict validation**: Validates proper thousands separators and decimal points
- 🚫 **Zero dependencies**: Lightweight with no external dependencies
- 📦 **TypeScript**: Full TypeScript support with type definitions
- 🧪 **Well tested**: Comprehensive test suite with 300+ test cases
- 📱 **Modern**: ES modules and CommonJS support
- ⚡ **High Performance**: 4.4M+ parses per second

### Why Choose @norbulcz/num-parse?

- **Smaller** (1.2KB gzipped, zero deps)
- **Stricter** (rejects invalid groupings)
- **Locale-flexible** (US, EU, Swiss, plain, space grouping, with currency symbols)
- **Safer** (never silently misparses into wrong numbers)
- **Simple** (no Intl dependency, no polyfills, no moment.js or numeral.js — just a single function)
- **Predictable** (invalid input → returns null, never throws)

## Performance

The library is highly optimized for performance:

- **4.4M+ parses per second** on modern hardware
- **0.0002ms average parse time** per operation
- **Pre-compiled regex patterns** for maximum efficiency
- **Zero dependencies** for minimal bundle impact

### Bundle Size
- **ESM**: ~1.2KB gzipped
- **CJS**: ~1.6KB gzipped
- **Types**: ~1.4KB

## Installation

```bash
npm install @norbulcz/num-parse
```

## Quickstart

```typescript
import { parseNumber } from '@norbulcz/num-parse';

// US format
parseNumber("1,234.56"); // → 1234.56

// European format  
parseNumber("1.234,56"); // → 1234.56

// Swiss format (both apostrophe types)
parseNumber("1'234.56"); // → 1234.56
parseNumber("1'234.56"); // → 1234.56 (curly apostrophe)

// Currency symbols (automatically removed)
parseNumber("₪1,234.56"); // → 1234.56
parseNumber("1,234.56$"); // → 1234.56

// Plain numbers (no separators)
parseNumber("1234.56"); // → 1234.56
parseNumber("1234"); // → 1234

// Space grouping
parseNumber("1 234.56"); // → 1234.56
parseNumber("1 234 567.89"); // → 1234567.89
```

## Usage

```typescript
import { parseNumber } from '@norbulcz/num-parse';

// US format
parseNumber('1,234.56');     // → 1234.56
parseNumber('12,345.67');    // → 12345.67

// European format
parseNumber('1.234,56');     // → 1234.56
parseNumber('12.345,67');    // → 12345.67

// Swiss format (apostrophe as thousands separator)
parseNumber("1'234.56");     // → 1234.56
parseNumber("1'234,56");      // → 1234.56

// Negative numbers
parseNumber('-1,234.56');    // → -1234.56
parseNumber('-1.234,56');    // → -1234.56

// Edge cases
parseNumber('.5');           // → 0.5
parseNumber(',5');           // → 0.5
parseNumber('1,000');        // → 1000
parseNumber('1.000');        // → 1000

// Currency symbols (automatically removed)
parseNumber('₪1,234.56');   // → 1234.56 (Israeli Shekel)
parseNumber('₿0.001');      // → 0.001 (Bitcoin)
parseNumber('₩1,234.56');   // → 1234.56 (Korean Won)
parseNumber('₹1,234.56');   // → 1234.56 (Indian Rupee)
parseNumber('1,234.56$');   // → 1234.56 (US Dollar at end)
parseNumber('1.234,56€');   // → 1234.56 (Euro at end)

// Scientific notation (disabled by default)
parseNumber('1.5e3');        // → null (disabled by default)
parseNumber('1.5e3', { allowScientific: true }); // → 1500

// Plain inputs & spaces
parseNumber('3333');         // → 3333
parseNumber('12345.67');     // → 12345.67
parseNumber('12345,67');     // → 12345.67
parseNumber('1 123.34');     // → 1123.34
parseNumber('2 276,00');     // → 2276 (NBSP)

// Invalid inputs return null
parseNumber('invalid');      // → null
parseNumber('1,23,456');     // → null (invalid grouping)
parseNumber("12'34");        // → null (invalid apostrophe grouping)
parseNumber('123.');         // → null (trailing decimal)
parseNumber('');             // → null
```

## Supported Formats

### US Format
- Thousands separator: comma (`,`)
- Decimal separator: period (`.`)
- Examples: `1,234.56`, `12,345.67`

### European Format
- Thousands separator: period (`.`)
- Decimal separator: comma (`,`)
- Examples: `1.234,56`, `12.345,67`

### Swiss Format
- Thousands separator: apostrophe (`'`)
- Decimal separator: period (`.`) or comma (`,`)
- Examples: `1'234.56`, `1'234,56`

### Currency Symbol Support
- Automatically removes all Unicode currency symbols using the `\p{Sc}` class
- Supports: `$`, `€`, `£`, `¥`, `₹`, `₽`, `¢`, `₪`, `₿`, `₩`, `₫`, `₴`, `₦`, `₵`, and many more
- Examples: `₪1,234.56` → `1234.56`, `₿0.001` → `0.001`

## API

### `parseNumber(value: string, options?: ParseOptions): number | null`

Parses a string and returns a number or `null` if the input is invalid.

**Parameters:**
- `value` (string): The string to parse
- `options` (ParseOptions, optional): Configuration options

**Returns:**
- `number`: The parsed number
- `null`: If the input is invalid or empty

### `ParseOptions`

```typescript
interface ParseOptions {
  /** Strip any Unicode currency symbol (e.g. $, €, ₪, ₿). Default: true */
  stripCurrency?: boolean;
  /** Allow Swiss-style apostrophe grouping (both ' and '). Default: true */
  allowApostrophe?: boolean;
  /** Allow scientific notation like 1.5e3. Default: false */
  allowScientific?: boolean;
}
```

### Options Examples

```typescript
// Default behavior
parseNumber(input, {
  stripCurrency: true,   // \p{Sc} removed by default
  allowApostrophe: true, // Swiss ' and ' grouping
  allowScientific: false // opt-in
});

// Disable currency stripping
parseNumber('$1,234.56', { stripCurrency: false }); // → null
parseNumber('1,234.56', { stripCurrency: false });  // → 1234.56

// Disable Swiss apostrophe format
parseNumber("1'234.56", { allowApostrophe: false }); // → null
parseNumber('1,234.56', { allowApostrophe: false }); // → 1234.56

// Enable scientific notation
parseNumber('1.5e3', { allowScientific: true });     // → 1500
parseNumber('1.5e-3', { allowScientific: true });     // → 0.0015

// Combine options
parseNumber('₪1'234.56', { 
  stripCurrency: true, 
  allowApostrophe: true, 
  allowScientific: false 
}); // → 1234.56
```

## Validation Rules

The parser enforces strict validation rules:

1. **Thousands separators** must be in groups of exactly 3 digits
2. **Decimal points** can only appear once
3. **Signs** (`+`/`-`) can only appear at the beginning
4. **Swiss apostrophes** must follow proper 3-digit grouping
5. **Mixed separators** are disambiguated by the last separator (decimal point)

## Examples

```typescript
// Valid inputs
parseNumber('1,234.56');        // ✅ 1234.56
parseNumber('1.234,56');        // ✅ 1234.56
parseNumber("1'234.56");        // ✅ 1234.56
parseNumber('1,000');           // ✅ 1000
parseNumber('1.000');           // ✅ 1000
parseNumber("1'000");           // ✅ 1000
parseNumber('-1,234.56');       // ✅ -1234.56
parseNumber('.5');              // ✅ 0.5
parseNumber(',5');              // ✅ 0.5

// Invalid inputs
parseNumber('1,23,456');        // ❌ null (invalid grouping)
parseNumber('1.23.456');        // ❌ null (invalid grouping)
parseNumber('1,234.56.78');     // ❌ null (multiple decimal points)
parseNumber('1-234.56');        // ❌ null (sign in middle)
parseNumber('1,234,56');        // ❌ null (ambiguous - could be 1,234,56 or 1,234.56)
parseNumber('');                // ❌ null (empty)
parseNumber('invalid');         // ❌ null (non-numeric)
```

## Performance Benchmarks

### Real-world Performance
```
Running performance benchmark...
Total time: 1170.28ms
Average time per parse: 0.0002ms
Parses per second: 4,443,387
Total operations: 5,200,000
```

### Test Cases Covered
- US format: `1,234.56`
- EU format: `1.234,56`
- Swiss format: `1'234.56`
- Scientific notation: `1.5e3`
- Currency symbols: `₪1,234.56`, `1,234.56$`
- Large numbers: `999,999,999.99`
- Small decimals: `0.001`

### Optimization Features
- **Pre-compiled regex patterns** - No regex compilation overhead
- **Method extraction** - Clean, maintainable code structure
- **Early returns** - Minimal processing for invalid inputs
- **Unicode optimization** - Efficient `\p{Sc}` class usage

## Development

```bash
# Install dependencies
npm install

# Run tests
npm test

# Run tests in watch mode
npm run test:watch

# Build the library
npm run build

# Development mode with watch
npm run dev
```

## Contributing

Contributions are welcome! Please open issues or pull requests on [GitHub](https://github.com/norbulcz/num-parse).

## License

MIT © Norbert Lorincz