当你有涉及到财税的需求时,可能会像我一样遇到转换数字价格为中文大写价格的需求,这里提供一个算法实现的思路。
确定输入边界
我们参考税务局网站的实现(截止于本文截稿日期 2023/11/10):
- 当我们输入超过 13 位的数字时会提示
整数位已超过最大值
。 - 输入 3 位小数时格式化为 0。
确定特定输入的输出值
在处理转换的过程可能会遇到一些比较特殊的输入值(或者说比较模糊),比如:
- 输入
0.01
输出壹分
- 输入
1.01
输出壹元零壹分
- 输入
0
输出零元整
以上输出均参考税务局网站的实现
Coding 思路
首先排除边界:
ts
function getChinesePrice(price: number) {
if (price === 0) return '零元整';
if (price >= 1e12) return '整数位已超过最大值';
}
第二步收集大写中文字符集:
这里
CHINESE_UNIT_MAP
与CHINESE_BIG_UNIT_MAP
的第一位为空是为了便于0
位取值(边界处理)。
ts
const CHINESE_NUMBER_MAP = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
const CHINESE_UNIT_MAP = ['', '拾', '佰', '仟'];
const CHINESE_BIG_UNIT_MAP = ['', '万', '亿'];
const CHINESE_SMALL_UNIT_MAP = ['角', '分', '厘'];
然后通过 split('.')
分割整数部与小数部:
ts
const priceStr = price.toString();
const priceArr = priceStr.split('.');
const integer = priceArr[0]; // 整数部
const decimal = priceArr[1]; // 小数部
转换整数部稍微复杂一些,我们先列出需要考虑的几点:
- 需要处理
万
和亿
这两种进位。 - 需要合并中间多余的
0
,比如输入1009
应当转换成壹仟零玖元整
而不是壹仟零零玖元整
。
综上所述,我的实现的思路如下:
壹万万
等于壹亿
,那么我们可以用壹万(10000)
作为进位,将当前单位的位置除以4
可得其对应的万字位 , 将当前单位取模4
可得其对应的个 十 百 千
位。- 合并多余的
0
的实现位,只在当前位不为0
的情况下转换字符,并跳过个、万、亿位。
代码实现:
ts
let chineseIntegerPrice = '';
let zeroCount = 0;
for (let i = 0; i < integer.length; i++) {
const num = +integer[i];
const unit = integer.length - i - 1; // 当前数字的单位
const quotient = Math.floor(unit / 4); // 1w为进位单位, 除 4 即为 万 亿
const remainder = unit % 4; // 1w为进位单位, 取模 4 即为 个 十 百 千
if (num === 0) {
zeroCount++;
} else {
// 处理前置的零
if (zeroCount > 0) chineseIntegerPrice += CHINESE_NUMBER_MAP[0];
zeroCount = 0;
chineseIntegerPrice += CHINESE_NUMBER_MAP[num] + CHINESE_UNIT_MAP[remainder];
}
// 跳过个、万、亿位
if (remainder === 0 && zeroCount < 4) {
chineseIntegerPrice += CHINESE_BIG_UNIT_MAP[quotient];
}
}
这种方式需要额外处理价格为小数时,整数部分不显示的情况:
ts
// 价格为小数时,整数部分不显示
if (price < 1) chineseIntegerPrice = '';
else chineseIntegerPrice += '元';
处理小数部只需要对位合并即可,需要注意的是,在税务局的实现中,输入 1.01
输出的是 壹元零壹分
:
ts
let chineseDecimalPrice = '';
if (!decimal) {
chineseDecimalPrice = '整';
} else {
let hasZero = false;
for (let i = 0; i < decimal.length; i++) {
const num = +decimal[i];
if (num) chineseDecimalPrice += CHINESE_NUMBER_MAP[num] + CHINESE_SMALL_UNIT_MAP[i];
else hasZero = true;
}
// 处理 1.01 -> 壹元零壹分 的情况
if (chineseIntegerPrice && hasZero) chineseIntegerPrice += '零';
}
最后合并整数部与小数部即可,完整代码参考下面 👇 的 Github 实现源码。