有一次在上海前端交流群看见有人在群里发了一个求助信息:
请用JavaScript语言编写一个函数,要求入口参数为数字, 取值范围是一位数整数,返回值是字符串,该函数的功能为:返回该数字对应的汉字,例如:输入数字6,返回汉字“六”;输入数字9,返回汉字“九”。
然后我立马丢了一个以前我写的一个转中文数字的angular过滤器代码
//- 小写数字转换成大写, 只处理到[0 ~ 99] function numberConvertToUppercase() { return function(num) { num = Number(num); var upperCaseNumber = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '百', '千', '万', '亿']; var length = String(num).length; if (length == 1) { return upperCaseNumber[num]; } else if (length == 2) { if (num == 10) { return upperCaseNumber[num]; } else if (num > 10 && num < 20) { return '十' + upperCaseNumber[String(num).charAt(1)]; } else { return upperCaseNumber[String(num).charAt(0)] + '十' + upperCaseNumber[String(num).charAt(1)].replace('零', ''); } } } }
接下来就有人回应:
wolf 你这种写法要命了
才99 就这么长, 如果 99999呢
然后我以项目当时需求就只到2位为由回应。后来自己去尝试写一个完整的转换方法。尝试了很多次总是有一些细节没有考虑全。
经过多次测试后下面给出一个我最终写出的一个完整版本,供参考:
/** * 阿拉伯数字转中文数字, * 如果传入数字时则最多处理到21位,超过21位js会自动将数字表示成科学计数法,导致精度丢失和处理出错 * 传入数字字符串则没有限制 * @param {number|string} digit */function toZhDigit(digit) { digit = typeof digit === 'number' ? String(digit) : digit; const zh = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']; const unit = ['千', '百', '十', '']; const quot = ['万', '亿', '兆', '京', '垓', '秭', '穰', '沟', '涧', '正', '载', '极', '恒河沙', '阿僧祗', '那由他', '不可思议', '无量', '大数']; let breakLen = Math.ceil(digit.length / 4); let notBreakSegment = digit.length % 4 || 4; let segment; let zeroFlag = [], allZeroFlag = []; let result = ''; while (breakLen > 0) { if (!result) { // 第一次执行 segment = digit.slice(0, notBreakSegment); let segmentLen = segment.length; for (let i = 0; i < segmentLen; i++) { if (segment[i] != 0) { if (zeroFlag.length > 0) { result += '零' + zh[segment[i]] + unit[4 - segmentLen + i]; // 判断是否需要加上 quot 单位 if (i === segmentLen - 1 && breakLen > 1) { result += quot[breakLen - 2]; } zeroFlag.length = 0; } else { result += zh[segment[i]] + unit[4 - segmentLen + i]; if (i === segmentLen - 1 && breakLen > 1) { result += quot[breakLen - 2]; } } } else { // 处理为 0 的情形 if (segmentLen == 1) { result += zh[segment[i]]; break; } zeroFlag.push(segment[i]); continue; } } } else { segment = digit.slice(notBreakSegment, notBreakSegment + 4); notBreakSegment += 4; for (let j = 0; j < segment.length; j++) { if (segment[j] != 0) { if (zeroFlag.length > 0) { // 第一次执行zeroFlag长度不为0,说明上一个分区最后有0待处理 if (j === 0) { result += quot[breakLen - 1] + zh[segment[j]] + unit[j]; } else { result += '零' + zh[segment[j]] + unit[j]; } zeroFlag.length = 0; } else { result += zh[segment[j]] + unit[j]; } // 判断是否需要加上 quot 单位 if (j === segment.length - 1 && breakLen > 1) { result += quot[breakLen - 2]; } } else { // 第一次执行如果zeroFlag长度不为0, 且上一划分不全为0 if (j === 0 && zeroFlag.length > 0 && allZeroFlag.length === 0) { result += quot[breakLen - 1]; zeroFlag.length = 0; zeroFlag.push(segment[j]); } else if (allZeroFlag.length > 0) { // 执行到最后 if (breakLen == 1) { result += ''; } else { zeroFlag.length = 0; } } else { zeroFlag.push(segment[j]); } if (j === segment.length - 1 && zeroFlag.length === 4 && breakLen !== 1) { // 如果执行到末尾 if (breakLen === 1) { allZeroFlag.length = 0; zeroFlag.length = 0; result += quot[breakLen - 1]; } else { allZeroFlag.push(segment[j]); } } continue; } } --breakLen; } return result;}
关于中文计数单位可以网上自行搜索。
上面的代码大体思路是:
从左至右,先把数字按万分位分组,每组加上对应的单位(万,亿, ...), 然后每个分组进行迭代。breakLen
表示能够分成多少个分组,notBreakSegment
表示当前已处理过的分组长度。while
循环中有一个if
判断,如果不存在result
,则说明是第一次处理,那么在处理上是有些不同的。首先,在segment
的赋值上,第一次是从0
开始,取notBreakSegment
的长度,后面每迭代一次notBreakSegment
都要在上一个值上加4
;其次,第一次处理不用判断上一个分组是否全为0
的情形,这里zeroFlag
表示每一个分组内存在0
的个数,allZeroFalg
表示当前分组前面出现的全为0
的分组的个数。此外,在第一次执行时,还处理了只传入为0
的情形。
每次处理segment[i]
时,都要先判断当前值是否为0
,为0
时则直接记录到zeroFlag
,然后进入下一次迭代,如果不为0
,首先得判断上一个数字是否为0
, 然后还得根据上一个0
是否位于上一个分组的末位,来添加quot,最后还需要清空标志位。如果当前分组全为0
,则标记allZeroFlag
,所以在下一个分组处理时,还需要判断上一个分组是否全为0
更多细节直接看代码,这里就不多作解释了。
接下来是中文转阿拉伯数字,这个处理起来比较简单,这里采用从右至左的方式对每一位进行迭代,直接上代码:
function zhDigitToArabic(digit) { const zh = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']; const unit = ['千', '百', '十']; const quot = ['万', '亿', '兆', '京', '垓', '秭', '穰', '沟', '涧', '正', '载', '极', '恒河沙', '阿僧祗', '那由他', '不可思议', '无量', '大数']; let result = 0, quotFlag; for (let i = digit.length - 1; i >= 0; i--) { if (zh.indexOf(digit[i]) > -1) { // 数字 if (quotFlag) { result += quotFlag * getNumber(digit[i]); } else { result += getNumber(digit[i]); } } else if (unit.indexOf(digit[i]) > -1) { // 十分位 if (quotFlag) { result += quotFlag * getUnit(digit[i]) * getNumber(digit[i - 1]); } else { result += getUnit(digit[i]) * getNumber(digit[i - 1]); } --i; } else if (quot.indexOf(digit[i]) > -1) { // 万分位 if (unit.indexOf(digit[i - 1]) > -1) { if (getNumber(digit[i - 1])) { result += getQuot(digit[i]) * getNumber(digit[i - 1]); } else { result += getQuot(digit[i]) * getUnit(digit[i - 1]) * getNumber(digit[i - 2]); quotFlag = getQuot(digit[i]); --i; } } else { result += getQuot(digit[i]) * getNumber(digit[i - 1]); quotFlag = getQuot(digit[i]); } --i; } } return result; // 返回中文大写数字对应的阿拉伯数字 function getNumber(num) { for (let i = 0; i < zh.length; i++) { if (zh[i] == num) { return i; } } } // 取单位 function getUnit(num) { for (let i = unit.length; i > 0; i--) { if (num == unit[i - 1]) { return Math.pow(10, 4 - i); } } } // 取分段 function getQuot(q) { for (var i = 0; i < quot.length; i++) { if (q == quot[i]) { return Math.pow(10, (i + 1) * 4); } } }}
说明:代码仅供参考,作者只写了一些特殊数字和随机数字进行测试,不能保证百分百准确,如果有问题请留言反馈。