@mahmud002/number-to-words-converter
Version:
A simple utility to convert numbers to their word representations in English and Persian, and vice versa. Includes Persian numerals conversion.
450 lines (394 loc) • 9.01 kB
text/typescript
const ONES = [
'',
'one',
'two',
'three',
'four',
'five',
'six',
'seven',
'eight',
'nine',
'ten',
'eleven',
'twelve',
'thirteen',
'fourteen',
'fifteen',
'sixteen',
'seventeen',
'eighteen',
'nineteen',
]
const TENS = ['', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety']
const SCALES = ['', 'thousand', 'million', 'billion', 'trillion']
// Persian number arrays
const PERSIAN_ONES = [
'',
'یک',
'دو',
'سه',
'چهار',
'پنج',
'شش',
'هفت',
'هشت',
'نه',
'ده',
'یازده',
'دوازده',
'سیزده',
'چهارده',
'پانزده',
'شانزده',
'هفده',
'هجده',
'نوزده',
]
const PERSIAN_TENS = ['', '', 'بیست', 'سی', 'چهل', 'پنجاه', 'شصت', 'هفتاد', 'هشتاد', 'نود']
const PERSIAN_HUNDREDS = [
'',
'صد',
'دویست',
'سیصد',
'چهارصد',
'پانصد',
'ششصد',
'هفتصد',
'هشتصد',
'نهصد',
]
const PERSIAN_SCALES = ['', 'هزار', 'میلیون', 'میلیارد', 'تریلیون']
// Persian numerals mapping
const PERSIAN_NUMERALS: { [key: string]: string } = {
'۰': '0',
'۱': '1',
'۲': '2',
'۳': '3',
'۴': '4',
'۵': '5',
'۶': '6',
'۷': '7',
'۸': '8',
'۹': '9',
}
// Persian numerals mapping (reverse)
const ENGLISH_TO_PERSIAN_NUMERALS: { [key: string]: string } = {
'0': '۰',
'1': '۱',
'2': '۲',
'3': '۳',
'4': '۴',
'5': '۵',
'6': '۶',
'7': '۷',
'8': '۸',
'9': '۹',
}
// English number words to numeric mapping
const ENGLISH_WORD_TO_NUMBER: { [key: string]: number } = {
zero: 0,
one: 1,
two: 2,
three: 3,
four: 4,
five: 5,
six: 6,
seven: 7,
eight: 8,
nine: 9,
ten: 10,
eleven: 11,
twelve: 12,
thirteen: 13,
fourteen: 14,
fifteen: 15,
sixteen: 16,
seventeen: 17,
eighteen: 18,
nineteen: 19,
twenty: 20,
thirty: 30,
forty: 40,
fifty: 50,
sixty: 60,
seventy: 70,
eighty: 80,
ninety: 90,
hundred: 100,
thousand: 1000,
million: 1000000,
billion: 1000000000,
trillion: 1000000000000,
}
// Persian number words to numeric mapping
const PERSIAN_WORD_TO_NUMBER: { [key: string]: number } = {
صفر: 0,
یک: 1,
دو: 2,
سه: 3,
چهار: 4,
پنج: 5,
شش: 6,
هفت: 7,
هشت: 8,
نه: 9,
ده: 10,
یازده: 11,
دوازده: 12,
سیزده: 13,
چهارده: 14,
پانزده: 15,
شانزده: 16,
هفده: 17,
هجده: 18,
نوزده: 19,
بیست: 20,
سی: 30,
چهل: 40,
پنجاه: 50,
شصت: 60,
هفتاد: 70,
هشتاد: 80,
نود: 90,
صد: 100,
دویست: 200,
سیصد: 300,
چهارصد: 400,
پانصد: 500,
ششصد: 600,
هفتصد: 700,
هشتصد: 800,
نهصد: 900,
هزار: 1000,
میلیون: 1000000,
میلیارد: 1000000000,
تریلیون: 1000000000000,
}
// English numerals to string mapping
const ENGLISH_NUMERALS: { [key: string]: string } = {
'0': 'zero',
'1': 'one',
'2': 'two',
'3': 'three',
'4': 'four',
'5': 'five',
'6': 'six',
'7': 'seven',
'8': 'eight',
'9': 'nine',
}
function convertChunk(num: number): string {
if (num === 0) return ''
let words = ''
// Handle hundreds
if (num >= 100) {
words += ONES[Math.floor(num / 100)] + ' hundred '
num %= 100
}
// Handle tens and ones
if (num > 0) {
if (words !== '') {
words += 'and '
}
if (num < 20) {
words += ONES[num]
} else {
words += TENS[Math.floor(num / 10)]
if (num % 10 > 0) {
words += '-' + ONES[num % 10]
}
}
}
return words.trim()
}
function convertPersianChunk(num: number): string {
if (num === 0) return ''
let words = ''
// Handle hundreds
if (num >= 100) {
words += PERSIAN_HUNDREDS[Math.floor(num / 100)] + ' '
num %= 100
}
// Handle tens and ones
if (num > 0) {
if (words !== '') {
words += 'و '
}
if (num < 20) {
words += PERSIAN_ONES[num]
} else {
words += PERSIAN_TENS[Math.floor(num / 10)]
if (num % 10 > 0) {
words += ' و ' + PERSIAN_ONES[num % 10]
}
}
}
return words.trim()
}
export function numberToWords(num: number): string {
if (num === 0) return 'zero'
if (num < 0) return 'negative ' + numberToWords(Math.abs(num))
let words = ''
let scaleIndex = 0
while (num > 0) {
const chunk = num % 1000
if (chunk > 0) {
const chunkWords = convertChunk(chunk)
words = chunkWords + (SCALES[scaleIndex] ? ' ' + SCALES[scaleIndex] + ' ' : '') + words
}
num = Math.floor(num / 1000)
scaleIndex++
}
return words.trim().toLowerCase()
}
export function numberToPersianWords(input: string | number): string {
// Convert Persian numerals to English numerals if string input
let num: number
if (typeof input === 'string') {
num = parseInt(
input
.split('')
.map((char) => PERSIAN_NUMERALS[char] || char)
.join('')
)
} else {
num = input
}
if (isNaN(num)) {
throw new Error('Invalid number input')
}
if (num === 0) return 'صفر'
if (num < 0) return 'منفی ' + numberToPersianWords(Math.abs(num))
let words = ''
let scaleIndex = 0
let hasValue = false
while (num > 0) {
const chunk = num % 1000
if (chunk > 0) {
const chunkWords = convertPersianChunk(chunk)
if (hasValue) {
words = chunkWords + ' ' + PERSIAN_SCALES[scaleIndex] + ' و ' + words
} else {
words =
chunkWords + (PERSIAN_SCALES[scaleIndex] ? ' ' + PERSIAN_SCALES[scaleIndex] : '') + words
hasValue = true
}
}
num = Math.floor(num / 1000)
scaleIndex++
}
return words.trim()
}
export function wordsToNumber(words: string): number {
// Remove extra spaces and convert to lowercase
words = words.toLowerCase().trim().replace(/\s+/g, ' ')
// Handle negative numbers
if (words.startsWith('negative ')) {
return -wordsToNumber(words.slice(9))
}
let total = 0
let currentNumber = 0
// Split into words and process each
const wordArray = words.split(/[\s-]+/)
for (let i = 0; i < wordArray.length; i++) {
const word = wordArray[i]
if (word === 'and') continue
const value = ENGLISH_WORD_TO_NUMBER[word]
if (value === undefined) {
throw new Error(`Unknown word: ${word}`)
}
if (value === 100) {
currentNumber = (currentNumber || 1) * value
} else if (value >= 1000) {
currentNumber = (currentNumber || 1) * value
total += currentNumber
currentNumber = 0
} else {
currentNumber += value
}
}
return total + currentNumber
}
export function persianWordsToNumber(words: string): number {
// Handle negative numbers
if (words.startsWith('منفی ')) {
return -persianWordsToNumber(words.slice(5))
}
let total = 0
let currentNumber = 0
// Split into words and process each
const wordArray = words.split(/\s+و\s+|\s+/)
for (let i = 0; i < wordArray.length; i++) {
const word = wordArray[i].trim()
const value = PERSIAN_WORD_TO_NUMBER[word]
if (value === undefined) {
throw new Error(`Unknown word: ${word}`)
}
if (value >= 1000) {
currentNumber = (currentNumber || 1) * value
total += currentNumber
currentNumber = 0
} else {
currentNumber += value
}
}
return total + currentNumber
}
export function stringToNumber(input: string): number {
// Try parsing as a regular number first
const num = Number(input)
if (!isNaN(num)) {
return num
}
// Convert Persian numerals to English numerals
const englishNumerals = input
.split('')
.map((char) => PERSIAN_NUMERALS[char] || char)
.join('')
const numFromPersian = Number(englishNumerals)
if (!isNaN(numFromPersian)) {
return numFromPersian
}
// Try parsing as English words
try {
return wordsToNumber(input)
} catch (e) {
// Try parsing as Persian words
try {
return persianWordsToNumber(input)
} catch (e2) {
throw new Error('Invalid number format')
}
}
}
export function numberToString(num: number, format: 'english' | 'persian' = 'english'): string {
if (format === 'persian') {
return numberToPersianWords(num)
}
return numberToWords(num)
}
export function numberToPersianNumerals(num: number): string {
return num
.toString()
.split('')
.map((digit) => ENGLISH_TO_PERSIAN_NUMERALS[digit] || digit)
.join('')
}
export function persianNumeralsToNumber(persianNumerals: string): number {
const englishNumerals = persianNumerals
.split('')
.map((char) => PERSIAN_NUMERALS[char] || char)
.join('')
return parseInt(englishNumerals)
}
export default {
numberToWords,
numberToPersianWords,
wordsToNumber,
persianWordsToNumber,
stringToNumber,
numberToString,
numberToPersianNumerals,
persianNumeralsToNumber,
}