preface

There is a recent need to convert Arabic numerals to uppercase Chinese numerals, such as 100 to 100 yuan. Don’t say much, roll up sleeves is dry, do wild!

The rules

First of all, let’s take a look at baidu Encyclopedia capitalization numbers, the description of capitalization rules:

  1. If the Chinese uppercase digits end in “yuan”, the word “whole” (or “zheng”) should be written after “yuan”, and the word “whole” (or “zheng”) can not be written after “Angle”. Capital numerals with “points” are not followed by the word “whole” (or “plus”).

  2. An example of an Arabic digit containing “0” is as follows:

    1. If there is a “0” in the middle of the number, write “0” in capital Chinese. For example, 1409.50 should be written as one thousand four hundred and nine yuan and five jiao.

    2. When there are several consecutive “zeros” in the middle of the number, only one “zero” can be written in the middle of the uppercase Chinese. For example, 6007.14 should be written as sixty-seven yuan fourteen cents.

    3. When the digit ten thousand and bit are “0”, or there are several consecutive “0” in the middle of the digit, ten thousand and bit are also “0”, but the digit thousands and corner are not “0”, the Chinese capital can only write a zero word, also can not write “zero”. If 1680.32, it should be written as ONE thousand six hundred and eighty eight Dollars and thirty-two cents, or one thousand six hundred and eighty eight dollars and thirty-two cents, or one hundred and seventy seven thousand and fifty-three cents, or one hundred and seventy seven thousand and fifty-three cents.

    4. When the digit Angle is “0” but the digit fraction is not “0”, the Chinese capital “yuan” should be followed by “0”. If 16409.02, it should be written as sixteen thousand four hundred nine dollars and two cents; Or 325.04, it should be three hundred and twenty two dollars and four cents.

Train of thought

At first glance, it’s confusing. So many rules. Actually, if you break it down a little bit, it doesn’t matter. Mainly according to the following steps to achieve:

  1. Numbers are divided into integer and decimal parts:

    1. The integer part is processed according to the four-digit classification method, which is a classification method with four digits as a number level, as shown in the figure:
    2. The decimal part is relatively easy, because there are only two digits, just match the corresponding string.
  2. Do the corresponding compatibility according to the rules, as full of compatibility with life.

implementation

Set a small goal, convert 100 million

We need to break the number into whole and decimal parts, and then break the two parts into numbers.

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);
Copy the code

We get two arrays like this:

configuration

  1. Uppercase numbers 0-9;

  2. Unit of count, the first digit is not displayed, so it is left empty;

  3. Series, followed by mega, jing, moat, etc., can be ignored;

const conf = {
    num: ["Zero"."One"."贰"."叁"."Boss"."Wu"."Lu"."Pure".""."Nine"].unit: [""."Pick up"."Hk"."仟"].level: [""."万"."亿"]};Copy the code

Four grade

Get a list of levels by four-digit levels: partially reverse the integer, process from the ones place, get units of count and assemble them, then cram a level, if the level is stuffed with four units of count, then cram ten thousand levels, and so on.

const levels = integer.reverse().reduce((pre, item, idx) = > {
    let level = pre[0] && pre[0].length < 4 ? pre[0] : [];

    // Get the count unit and assemble it
    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;
    }

    returnpre; } []);Copy the code

So we get an array like this:

Integer assembly

Iterate over the series list, concatenating the superior number of names at the same time.

const _integer = levels.reduce((pre, item, idx) = > {
    // get the series
    let _level = conf.level[levels.length - idx - 1];
    let _item = item.join("");

    return pre + _item + _level;
}, "");

console.log({ _integer });
Copy the code

At this point we get something like this:

This is obviously not the result we want, many more zeros, at the same time zero million is also redundant, do compatibility:

const _integer = levels.reduce((pre, item, idx) = > {
    let _level = conf.level[levels.length - idx - 1];
    
    // Set the part of consecutive zeros to a single zeros
    let _item = item.join("").replace(\ + / 1 g/(zero)."$1");

    // If there is only one zero at the level, remove the level
    if (_item === "Zero") {
        _item = "";
        _level = "";

        // Otherwise, if the end is zero, the zero is removed
    } else if (_item[_item.length - 1= = ="Zero") {
        _item = _item.slice(0, _item.length - 1);
    }

    return pre + _item + _level;
}, "");

console.log({ _integer });
Copy the code

Now the result is basically OK:

But according to rule 1, we’re missing an entire element. The problem is not big, we deal with the small part, and then do compatibility.

The decimal part

Just iterate over the decimal part.

let _decimal = decimal
    .map((item, idx) = > {
        const unit = ["Points"."Angle"];
        const_unit = item ! = ="0" ? unit[unit.length - idx - 1] : "";

        return `${conf.num[item]}${_unit}`;
    })
    .join("");
Copy the code

The output

Finally missed before the whole word up, to this small goal of a hundred million achieved.

// If the value is an integer, add a whole word
return `${_integer}Yuan ` + (_decimal || "The whole");
Copy the code

test

When the code is finished, it can only be said that half of it is finished. Only when the test is passed can it be said that the function is realized.

number2text(100000000); // 100 million yuan only
number2text(1409.5); // One thousand four hundred and nine dollars and five jiao
number2text(6007.14); // Sixty-seven dollars and fourteen cents
number2text(1680.32); // One thousand six hundred and eighty eight yuan and thirty-two cents
number2text(107000.53); // One hundred and seventy-seven thousand dollars and fifty-three cents
number2text(16409.02); // Sixteen thousand four hundred and nine dollars and two cents
number2text(325.04); Three hundred and twenty two dollars and four cents
Copy the code

1680.32 can be written as one thousand six hundred and eighty eight dollars and thirty-two cents, or one thousand six hundred and eighty eight dollars and thirty-two cents.

The complete code

/ * * * @ description Numbers in Chinese digital * * @ param {Number | String} num Number (positive integer) * @ param {String} type text type, the lower | upper, Default upper * * @example number2Text (100000000) => "100 million only" */
export const number2text = (number, type = "upper") = > {
    / / configuration
    const confs = {
        lower: {
            num: ["Zero"."一"."二"."Three"."Four"."Five"."Six"."Seven"."Eight"."Nine"].unit: [""."Ten"."Best"."Thousand"."万"].level: [""."万"."亿"]},upper: {
            num: ["Zero"."One"."贰"."叁"."Boss"."Wu"."Lu"."Pure".""."Nine"].unit: [""."Pick up"."Hk"."仟"].level: [""."万"."亿"]},decimal: {
            unit: ["Points"."Angle"]},maxNumber: 999999999999.99
    };

    // Filter invalid parameters
    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("");

    // Four levels
    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;
        }

        returnpre; } []);// The integer part
    const _integer = levels.reduce((pre, item, idx) = > {
        let _level = conf.level[levels.length - idx - 1];
        let _item = item.join("").replace(\ + / 1 g/(zero)."$1"); // Set the part of consecutive zeros to a single zeros

        // If there is only one zero at the level, remove the level
        if (_item === "Zero") {
            _item = "";
            _level = "";

            // Otherwise, if the end is zero, the zero is removed
        } else if (_item[_item.length - 1= = ="Zero") {
            _item = _item.slice(0, _item.length - 1);
        }

        return pre + _item + _level;
    }, "");

    // The decimal part
    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("");

    // If the value is an integer, add a whole word
    return `${_integer}Yuan ` + (_decimal || "The whole");
};
Copy the code