js如何实现阿拉伯数字转中文大写数字

前言

最近有个需求,需要把阿拉伯数字转成中文大写数字,比如:100 转换成壹佰元。话不多说,撸起袖子就是干,做野!

规则

首先我们看看百度百科大写数字,对大写规则的描述:

  1. 中文大写数字到"元"为止的,在"元"之后,应写"整"(或"正")字,在"角"之后,可以不写"整"(或"正")字。大写数字有"分"的,“分"后面不写"整”(或"正")字。

  2. 阿拉伯数字中有"0"时,举例如下:

    1. 数字中间有"0"时,中文大写要写"零"字,如 1409.50,应写成壹仟肆佰零玖元伍角

    2. 数字中间连续有几个"0"时,中文大写中间可以只写一个"零"字,如 6007.14,应写成陆仟零柒元壹角肆分

    3. 数字万位和元位是"0",或者数字中间连续有几个"0",万位、元位也是"0",但千位、角位不是"0"时,中文大写可以只写一个零字,也可以不写"零"字。如 1680.32,应写成壹仟陆佰捌拾元零叁角贰分,或者写成壹仟陆佰捌拾元叁角贰分,又如 107000.53,应写成壹拾万柒仟元零伍角叁分,或者写成壹拾万零柒仟元伍角叁分

    4. 数字角位是"0",而分位不是"0"时,中文大写"元"后面应写"零"字。如 16409.02,应写成壹万陆仟肆佰零玖元零贰分;又如 325.04,应写成叁佰贰拾伍元零肆分

思路

咋一看有点懵,这么多规则。其实稍微拆分下,问题不大。主要按以下几个步骤来实现:

  1. 数字分成整数跟小数两个部分:

    1. 整数部分,按四位分级法来处理,四位分级法就是以四位数为一个数级的分级方法,如图所示:
    2. 小数部分,就相对简单点,因为只有两位,直接匹配对应的字符串即可。
  2. 根据规则做相应的兼容,跟人生一样充满兼容。

实现

定个小目标,转换一个亿

我们需要将数字拆成整数部分与小数部分两部分,然后再把这两部分再拆成一个个数字。

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);

于是我们得到这样两个数组:

配置
  1. 大写数字的 0-9;

  2. 计数单位,第一位不显示,所以空出来;

  3. 级数,后面是兆、京、垓等,基本不用可以忽视;

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 || "整");
};
  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值