前言
最近有个需求,需要把阿拉伯数字转成中文大写数字,比如:100 转换成壹佰元。话不多说,撸起袖子就是干,做野!
规则
首先我们看看百度百科大写数字
,对大写规则的描述:
-
中文大写数字到"元"为止的,在"元"之后,应写"整"(或"正")字,在"角"之后,可以不写"整"(或"正")字。大写数字有"分"的,“分"后面不写"整”(或"正")字。
-
阿拉伯数字中有"0"时,举例如下:
-
数字中间有"0"时,中文大写要写"零"字,如
1409.50
,应写成壹仟肆佰零玖元伍角
。 -
数字中间连续有几个"0"时,中文大写中间可以只写一个"零"字,如
6007.14
,应写成陆仟零柒元壹角肆分
。 -
数字万位和元位是"0",或者数字中间连续有几个"0",万位、元位也是"0",但千位、角位不是"0"时,中文大写可以只写一个零字,也可以不写"零"字。如
1680.32
,应写成壹仟陆佰捌拾元零叁角贰分
,或者写成壹仟陆佰捌拾元叁角贰分
,又如107000.53
,应写成壹拾万柒仟元零伍角叁分
,或者写成壹拾万零柒仟元伍角叁分
。 -
数字角位是"0",而分位不是"0"时,中文大写"元"后面应写"零"字。如
16409.02
,应写成壹万陆仟肆佰零玖元零贰分
;又如325.04
,应写成叁佰贰拾伍元零肆分
。
-
思路
咋一看有点懵,这么多规则。其实稍微拆分下,问题不大。主要按以下几个步骤来实现:
-
数字分成整数跟小数两个部分:
- 整数部分,按四位分级法来处理,四位分级法就是以四位数为一个数级的分级方法,如图所示:
- 小数部分,就相对简单点,因为只有两位,直接匹配对应的字符串即可。
- 整数部分,按四位分级法来处理,四位分级法就是以四位数为一个数级的分级方法,如图所示:
-
根据规则做相应的兼容,跟人生一样充满兼容。
实现
定个小目标,转换一个亿
我们需要将数字拆成整数部分与小数部分两部分,然后再把这两部分再拆成一个个数字。
function number2text(number) {
const numbers = String(Number(number).toFixed(2)).split(".");
const integer = numbers[0].split("");
const decimal = Number(numbers[1]) === 0 ? [] : numbers[1].split("");
console.log({ integer, decimal });
}
number2text(100000000);
于是我们得到这样两个数组:
配置
-
大写数字的 0-9;
-
计数单位,第一位不显示,所以空出来;
-
级数,后面是兆、京、垓等,基本不用可以忽视;
const conf = {
num: ["零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"],
unit: ["", "拾", "佰", "仟"],
level: ["", "万", "亿"]
};
四位分级
通过四位分级获取级数列表:将整数部分反转,从个位开始处理,获取计数单位并组装,然后塞进个级,如果个级塞满 4 个计数单位,则塞进万级,以此类推。
const levels = integer.reverse().reduce((pre, item, idx) => {
let level = pre[0] && pre[0].length < 4 ? pre[0] : [];
// 获取计数单位并组装
let value =
item === "0" ? conf.num[item] : conf.num[item] + conf.unit[idx % 4];
level.unshift(value);
if (level.length === 1) {
pre.unshift(level);
} else {
pre[0] = level;
}
return pre;
}, []);
于是我们得到这样一个数组:
整数组装
遍历级数列表,同时拼接上级数名。
const _integer = levels.reduce((pre, item, idx) => {
// 获取级数
let _level = conf.level[levels.length - idx - 1];
let _item = item.join("");
return pre + _item + _level;
}, "");
console.log({ _integer });
此时我们得到这样一个结果:
这显然不是我们想要的结果,多了很多零
,同时零万
也是多余,做下兼容:
const _integer = levels.reduce((pre, item, idx) => {
let _level = conf.level[levels.length - idx - 1];
// 连续多个零字的部分设置为单个零字
let _item = item.join("").replace(/(零)\1+/g, "$1");
// 如果这一级只有一个零字,则去掉这级
if (_item === "零") {
_item = "";
_level = "";
// 否则如果末尾为零字,则去掉这个零字
} else if (_item[_item.length - 1] === "零") {
_item = _item.slice(0, _item.length - 1);
}
return pre + _item + _level;
}, "");
console.log({ _integer });
此时的结果基本 OK 了:
但是根据第 1 条规则,还少了个整元
。问题不大,我们处理完小数部分,再做兼容。
小数部分
小数部分简单遍历即可。
let _decimal = decimal
.map((item, idx) => {
const unit = ["分", "角"];
const _unit = item !== "0" ? unit[unit.length - idx - 1] : "";
return `${conf.num[item]}${_unit}`;
})
.join("");
输出
最后把之前漏掉的整元
字补上,到此小目标一个亿
实现了。
// 如果是整数,则补个整字
return `${_integer}元` + (_decimal || "整");
测试
代码写完只能说完成一半,只有测试通过才能说,这个功能实现了,我们按照规则的示例测试下:
number2text(100000000); //壹亿元整
number2text(1409.5); //壹仟肆佰零玖元伍角零
number2text(6007.14); //陆仟零柒元壹角肆分
number2text(1680.32); //壹仟陆佰捌拾元叁角贰分
number2text(107000.53); //壹拾万柒仟元伍角叁分
number2text(16409.02); //壹万陆仟肆佰零玖元零贰分
number2text(325.04); //叁佰贰拾伍元零肆分
1680.32
可以写成壹仟陆佰捌拾元零叁角贰分
,或者写成壹仟陆佰捌拾元叁角贰分
,测试通过,收工!
完整代码
/**
* @description 数字转中文数码
*
* @param {Number|String} num 数字[正整数]
* @param {String} type 文本类型,lower|upper,默认upper
*
* @example number2text(100000000) => "壹亿元整"
*/
export const number2text = (number, type = "upper") => {
// 配置
const confs = {
lower: {
num: ["零", "一", "二", "三", "四", "五", "六", "七", "八", "九"],
unit: ["", "十", "百", "千", "万"],
level: ["", "万", "亿"]
},
upper: {
num: ["零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"],
unit: ["", "拾", "佰", "仟"],
level: ["", "万", "亿"]
},
decimal: {
unit: ["分", "角"]
},
maxNumber: 999999999999.99
};
// 过滤不合法参数
if (Number(number) > confs.maxNumber) {
console.error(
`The maxNumber is ${confs.maxNumber}. ${number} is bigger than it!`
);
return false;
}
const conf = confs[type];
const numbers = String(Number(number).toFixed(2)).split(".");
const integer = numbers[0].split("");
const decimal = Number(numbers[1]) === 0 ? [] : numbers[1].split("");
// 四位分级
const levels = integer.reverse().reduce((pre, item, idx) => {
let level = pre[0] && pre[0].length < 4 ? pre[0] : [];
let value =
item === "0" ? conf.num[item] : conf.num[item] + conf.unit[idx % 4];
level.unshift(value);
if (level.length === 1) {
pre.unshift(level);
} else {
pre[0] = level;
}
return pre;
}, []);
// 整数部分
const _integer = levels.reduce((pre, item, idx) => {
let _level = conf.level[levels.length - idx - 1];
let _item = item.join("").replace(/(零)\1+/g, "$1"); // 连续多个零字的部分设置为单个零字
// 如果这一级只有一个零字,则去掉这级
if (_item === "零") {
_item = "";
_level = "";
// 否则如果末尾为零字,则去掉这个零字
} else if (_item[_item.length - 1] === "零") {
_item = _item.slice(0, _item.length - 1);
}
return pre + _item + _level;
}, "");
// 小数部分
let _decimal = decimal
.map((item, idx) => {
const unit = confs.decimal.unit;
const _unit = item !== "0" ? unit[unit.length - idx - 1] : "";
return `${conf.num[item]}${_unit}`;
})
.join("");
// 如果是整数,则补个整字
return `${_integer}元` + (_decimal || "整");
};