number-reads-cn
Version:
数字中文读法转换工具(电话读法、数值读法、序列读法、金额读法、大写读法)
195 lines (171 loc) • 5.11 kB
text/typescript
const digitToChineseMap: Record<string, string> = {
'0': '零',
'1': '一',
'2': '二',
'3': '三',
'4': '四',
'5': '五',
'6': '六',
'7': '七',
'8': '八',
'9': '九',
}
const digitToPhoneMap: Record<string, string> = {
'0': '零',
'1': '夭',
'2': '二',
'3': '三',
'4': '四',
'5': '五',
'6': '六',
'7': '七',
'8': '八',
'9': '九',
}
const digitToUppercaseMap: Record<string, string> = {
'0': '零',
'1': '壹',
'2': '贰',
'3': '叁',
'4': '肆',
'5': '伍',
'6': '陆',
'7': '柒',
'8': '捌',
'9': '玖',
}
/**
* 数值中文大写(金额式读法)—— 支持亿以内
*/
const numberToChineseValue = (numStr: string) => {
const num = Number(numStr)
if (isNaN(num)) return numStr
const digitToChinese = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']
const unit = ['', '十', '百', '千']
const sectionUnit = ['', '万', '亿', '兆']
const toSection = (sectionNum: string) => {
let str = ''
let zero = true
const sectionArr = sectionNum.split('').reverse()
for (let i = 0; i < sectionArr.length; i++) {
const n = parseInt(sectionArr[i])
if (n === 0) {
if (!zero) {
zero = true
str = digitToChinese[0] + str
}
} else {
zero = false
str = digitToChinese[n] + unit[i] + str
}
}
return str.replace(/零+$/g, '')
}
const numArr = numStr.split('').reverse().join('').match(/.{1,4}/g) || []
let result = ''
for (let i = 0; i < numArr.length; i++) {
const section = toSection(numArr[i].split('').reverse().join(''))
if (section !== '') {
result = section + sectionUnit[i] + result
} else {
if (!result.startsWith('零')) result = '零' + result
}
}
result = result.replace(/^一十/, '十')
result = result.replace(/零+/g, '零')
result = result.replace(/零+$/g, '')
return result
}
/**
* 获取所有中文数字读法
*/
export const getAllNumberReads = (input: string): { label: string; value: string }[] => {
const phoneRead = input
.split('')
.map(char => digitToPhoneMap[char] || char)
const phoneSegments = [
phoneRead.slice(0, 3).join(''),
phoneRead.slice(3, 7).join(''),
phoneRead.slice(7).join(''),
]
const telephone = phoneSegments.filter(s => s).join(' ')
const number = numberToChineseValue(input)
const currency = number + '元'
const sequence = input
.split('')
.map(char => digitToChineseMap[char] || char)
.join(' ')
const upper = input
.split('')
.map(char => digitToUppercaseMap[char] || char)
.join('')
return [
{ label: '电话读法', value: telephone },
{ label: '数值读法', value: number },
{ label: '金额读法', value: currency },
{ label: '序列读法', value: sequence },
{ label: '大写读法', value: upper },
]
}
/**
* 将中文数字读法(如:一百八十六)反向转为数字(如:186)
* 暂只支持简体数字读法,不支持金额/大写/序列等
*/
export const chineseToNumber = (chinese: string): number | null => {
const charToDigit: Record<string, number> = {
'零': 0, '一': 1, '二': 2, '三': 3, '四': 4,
'五': 5, '六': 6, '七': 7, '八': 8, '九': 9
}
const unitMap: Record<string, number> = {
'十': 10,
'百': 100,
'千': 1000,
'万': 10000,
'亿': 100000000
}
let result = 0
let section = 0
let number = 0
let unit = 1
for (let i = chinese.length - 1; i >= 0; i--) {
const char = chinese[i]
if (char in charToDigit) {
number = charToDigit[char]
if (unit > 1) {
section += number * unit
unit = 1
} else {
section += number
}
} else if (char in unitMap) {
unit = unitMap[char]
if (unit >= 10000) {
result += section * unit
section = 0
}
} else {
// 忽略非数字字符
}
}
result += section
return isNaN(result) ? null : result
}
/**
* 支持 getAllNumberReads 返回的所有读法反向转为数字
*/
export const chineseReadToNumber = (read: string): number | null => {
// 去除空格和单位
const cleanRead = read.replace(/[元\s]/g, '')
// 如果是序列或电话读法(逐个数字中文)
if (/^[零一二三四五六七八九夭]+$/.test(cleanRead)) {
const map: Record<string, string> = { '夭': '1', '零': '0', '一': '1', '二': '2', '三': '3', '四': '4', '五': '5', '六': '6', '七': '7', '八': '8', '九': '9' }
return Number(cleanRead.split('').map(c => map[c] || '').join('')) || null
}
// 如果是大写读法(壹贰叁)
if (/^[壹贰叁肆伍陆柒捌玖零]+$/.test(cleanRead)) {
const map: Record<string, string> = { '壹': '1', '贰': '2', '叁': '3', '肆': '4', '伍': '5', '陆': '6', '柒': '7', '捌': '8', '玖': '9', '零': '0' }
return Number(cleanRead.split('').map(c => map[c] || '').join('')) || null
}
// 否则用中文数值解析(如 一百八十六)
return chineseToNumber(cleanRead)
}