金额转化中文算法

最近要项目中用到了把数字类型的金额(1029.89元)转换成中文书写的方式(一仟零贰拾玖点八九元),参考了一些其他人写的算法,总觉得有些不太完善或者不严谨,例如10100转换成“十万一千元”,还是“十万零一千元”。我看到的一些算法都是转换成了前者,甚至iOS开发中支持中文转换的Api也是转换成了前者,但当我请教公司的财务同学时,给出的答案应该是后者。
所以,把自己写的转换过程分享出来,可能写的不是特别漂亮,欢迎大家指导。

抽象

第一步:抽象

面向对象编程中最重要的思想就是抽象,抽象成代码表示。

10100 —> 十万零一千元

中文数学计数采用的方式是
1、数字+权位。如100中 1 佰,1为数字位,佰为权位。权位包括:个、拾、佰、仟。
2、4位为一节,每一节有节权位,如:万、亿、兆。

所以根据以上特性,将每一位10100中的每一位抽象成中文的(数字+权位)。节权位没有数字,只有权位。


 /**
     * 中文数学计数
     * 数字+权位
     * 100中 1 佰,1为数字位,佰为权位
     * 权位包括 个 拾 佰 仟
     * 4位为一节,每一节有节权位如 万 亿 兆
     */
    private class ChinaMath {
        private String num;
        private String weight;

        public ChinaMath(String num, String weight) {
            this.num = num;
            this.weight = weight;
        }

        public String getNum() {
            return num;
        }

        public String getWeight() {
            return weight;
        }

        /**
         * 是否是节权位
         *
         * @return 当num为空时,返回true,否则返回false
         */
        public boolean isSectionWeight() {
            return (num == null || "".equalsIgnoreCase(num.trim()));
        }

        @Override
        public String toString() {
            return "ChinaMath{" +
                    "num='" + num + '\'' +
                    ", weight='" + weight + '\'' +
                    '}';
        }
    }

转换

第二步:转换

先遍历整个金额,简单的转换为中文表示后的列表ChinaMath[]。
如:11001 —> 壹 万 壹仟 零佰 零拾 壹

String integerPart = "10001";
List intPart = new ArrayList<>();
        //从低位到高位遍历
        for (int i = len - 1; i >= 0; i--) {
            //数字的地位
            int lowerIndex = len - i - 1;
            //字符串的高位
            char lowerChar = integerPart.charAt(i);

            int remainderDivide4 = lowerIndex % 4;

            if (remainderDivide4 == 0) {
                intPart.add(0, new ChinaMath("", unit[lowerIndex / 4]));
            }
            intPart.add(0, new ChinaMath(transferSingleNum(Character.getNumericValue(lowerChar)), places[remainderDivide4]));
        }

过滤+转换

第三步:过滤+转换

这一步是最麻烦的,我们要以第二步的结果为基础,再根据中文的金额读写特点进行加工。
例如:

  1. 金额中间有多个0,只读一个零
  2. 金额末尾有多个0,都不读
  3. 10要读拾,不能读成壹拾
  4. 文章开头提到,节权位前的零不能省

List result = new ArrayList<>();
int chinaMatchLen = intPart.size();
        for (int i = 0; i < chinaMatchLen; i++) {
            ChinaMath chinaMath = intPart.get(i);
            String num = chinaMath.getNum();
            String weight = chinaMath.getWeight();
            if (isNotNull(num)) {
                //如果数字是0,则判断下一个数字或者是否是0 或者是否是节权位
                //如果不是,则现在要添加一个0,否则不添加
                if (num.equalsIgnoreCase(CHINEASE_DIGIT[0])) {
                    String nextNumOrSectionWeight = intPart.get(i + 1).getNum();
                    if (!CHINEASE_DIGIT[0].equalsIgnoreCase(nextNumOrSectionWeight)) {
                        integerList.add(CHINEASE_DIGIT[0]);
                    }
                } else {
                    //添加数字和权位
                    if (num.equalsIgnoreCase(CHINEASE_DIGIT[1]) && weight.equalsIgnoreCase(places[1]) && i == 0) {
                        //如果十位是1,则读成拾,而不是壹拾
                        if (isNotNull(weight)) {
                            integerList.add(weight);
                        }
                    } else {
                        integerList.add(num);
                        if (isNotNull(weight)) {
                            integerList.add(weight);
                        }
                    }
                }
            }

            //节权位并且不为空,因为个位也是节权位,但没有实际值
            boolean isSectionWight = chinaMath.isSectionWeight();
            if (isSectionWight) {
                String lastNum = intPart.get(i - 1).getNum();
                //如果是正常的节权位
                if (isNotNull(chinaMath.getWeight())) {
                    String nextNum = intPart.get(i + 1).getNum();

                    if (lastNum.equalsIgnoreCase(CHINEASE_DIGIT[0])) {
                        if (!nextNum.equalsIgnoreCase(CHINEASE_DIGIT[0])) {
                            //前一位是0,后一位不是0,则交换节权位和前一位的位置,例0万 -> 万0
                            integerList.set(integerList.size() - 1, chinaMath.getWeight());
                            integerList.add(CHINEASE_DIGIT[0]);
                        } else {
                            //前一位是0,后一位是0,需要把前一位的0去掉
                            integerList.remove(integerList.size() - 1);
                            integerList.add(chinaMath.getWeight());
                        }
                    } else {
                        //前一位不是0, 直接添加节权位
                        integerList.add(chinaMath.getWeight());
                    }
                } else {
                    //最后一个节权位,如果上一位是0,并且不仅有一个0,移除个位多余的0
                    if (lastNum.equalsIgnoreCase(CHINEASE_DIGIT[0]) && integerList.size() > 1) {
                        integerList.remove(integerList.size() - 1);
                    }
                }
            }
        }
        result.addAll(0, integerList);

        result.add(YUAN);

测试


0.12 -> [zero, point, one, two, yuan]
0.02 -> [zero, point, zero, two, yuan]
1005.20 -> [one, thousand, zero, five, point, two, yuan]
1234.30 -> [one, thousand, two, hundred, three, ten, four, point, three, yuan]
120 3023.02 -> [one, hundred, two, ten, wan, zero, three, thousand, zero, two, ten, three, point, zero, two, yuan]
1000 0003.02 -> [one, thousand, wan, zero, three, point, zero, two, yuan]
10.13 -> [ten, point, one, three, yuan]
1000 0703.02 -> [one, thousand, wan, zero, seven, hundred, zero, three, point, zero, two, yuan]
0010.13 -> [ten, point, one, three, yuan]
110.13 -> [one, hundred, one, ten, point, one, three, yuan]
12100010.13 -> [one, thousand, two, hundred, one, ten, wan, zero, one, ten, point, one, three, yuan]

从测试结果可以看出,所有特殊情况的金额都转换正确了。初次之外,在转换之前做了一些严格的参数判断和简单的格式化操作,保证输入的合法性。

附完整源码:


/**
 * 数字转换成大写汉字
 * 

* 最大支持到千万 *

* 1234 5678 .90 *

*

* Created by joye on 2017/6/28. */ public class DigitTransfer2Chinese { /** * 小数点 */ protected final String DECIMAL_POINT = "."; /** * 中文大写数字 */ public static final String[] CHINEASE_DIGIT = {"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"}; public static final String[] unit = {"", "wan"}; public static final String[] places = {"", "ten", "hundred", "thousand"}; public static final String DIAN = "point"; public static final String YUAN = "yuan"; /** * 支持转义的小数最大精确度 */ private final int MAX_DECIMAL_PRECISION = 2; /** * 支持转义的最大位数 */ private final int MAX_DIGIT_BITS = 8; /** * 将数字转换为中文大写文字 * * @param digit 小数 * @return 中文大写 * @throws IllegalArgumentException 参数异常 */ public List transfer(float digit) throws IllegalArgumentException { if (digit <= 0) { throw new IllegalArgumentException("the param must be greater than zero, but the digit is " + digit); } return transfer(String.valueOf(digit)); } /** * 将字符串类型数字转换为中文大写文字 * * @param digit 字符串类型的数字 * @return 中文大写 * @throws IllegalArgumentException 参数异常 */ public List transfer(String digit) throws IllegalArgumentException { //判断是否为空 if (digit == null || digit.length() == 0) { throw new IllegalArgumentException("param must not be empty, but the digit is " + digit); } //去除空格 digit = removeBlank(digit); //判断是否包含非法字符(小数点除外) if (!isAllNum(digit)) { throw new IllegalArgumentException("param must all be number, but the digit is " + digit); } //判断是否超出最大位数 if (isOverMaxBits(digit)) { throw new IllegalArgumentException("param's max bits is " + MAX_DIGIT_BITS + ", but the digit is " + digit); } //判断是否超出小数精确度 if (isOverMaxDecimalPrecision(digit)) { throw new IllegalArgumentException("param's max decimal precision is " + MAX_DECIMAL_PRECISION + ", but the digit is " + digit); } //判断是否以小数点结尾 if (isDecimalPointEnding(digit)) { throw new IllegalArgumentException("param can not end with . , but the digit is " + digit); } digit = removeInvalidZero(digit); return transferInternal(digit); } private String reverse(String source) { int len = source.length(); if (len == 0 || len == 1) { return source; } char[] chars = source.toCharArray(); int replaceTime = len / 2; for (int i = 0; i < replaceTime; i++) { char temp = chars[i]; chars[i] = chars[len - 1 - i]; chars[len - 1 - i] = temp; } return String.valueOf(chars); } private String removeBlank(String digit) { return digit.replace(" ", ""); } //是否以小数点结尾 private boolean isDecimalPointEnding(String digit) { return digit.endsWith(DECIMAL_POINT); } //是否超出最大小数精确度 private boolean isOverMaxDecimalPrecision(String digit) { if (!digit.contains(DECIMAL_POINT)) { return false; } String decimalPartWithPoint = digit.substring(digit.indexOf(DECIMAL_POINT), digit.length()); return decimalPartWithPoint.length() > MAX_DECIMAL_PRECISION + 1; } //是否超出最大位数 private boolean isOverMaxBits(String digit) { String integerPart = digit; if (digit.contains(DECIMAL_POINT)) { integerPart = digit.substring(0, digit.indexOf(DECIMAL_POINT)); } return integerPart.length() > MAX_DIGIT_BITS; } //是否全是数字 小数点除外 private boolean isAllNum(String digit) { int len = digit.length(); for (int i = 0; i < len; i++) { char temp = digit.charAt(i); if (!String.valueOf(temp).equals(DECIMAL_POINT) && (temp < '0' || temp > '9')) { return false; } } return true; } //去除无效的0 private String removeInvalidZero(String origin) { //去除末尾的0 if (origin.contains(DECIMAL_POINT)) { if (origin.endsWith(".00") || origin.endsWith(".0")) { origin = origin.substring(0, origin.indexOf(DECIMAL_POINT)); } while (origin.endsWith("0")) { origin = origin.substring(0, origin.length() - 1); } } while ("0".equalsIgnoreCase(String.valueOf(origin.charAt(0))) && (origin.length() >= 2 && !DECIMAL_POINT.equalsIgnoreCase(String.valueOf(origin.charAt(1))))) { origin = origin.substring(1, origin.length()); } return origin; } private List transferInternal(String digit) { List result = new ArrayList<>(); String integerPart = digit; if (digit.contains(DECIMAL_POINT)) { result = transferDecimal(digit.substring(digit.indexOf(DECIMAL_POINT), digit.length())); integerPart = digit.substring(0, digit.indexOf(DECIMAL_POINT)); } int len = integerPart.length(); List integerList = new ArrayList<>(); List intPart = new ArrayList<>(); //从低位到高位遍历 for (int i = len - 1; i >= 0; i--) { //数字的地位 int lowerIndex = len - i - 1; //字符串的高位 char lowerChar = integerPart.charAt(i); int remainderDivide4 = lowerIndex % 4; if (remainderDivide4 == 0) { intPart.add(0, new ChinaMath("", unit[lowerIndex / 4])); } intPart.add(0, new ChinaMath(transferSingleNum(Character.getNumericValue(lowerChar)), places[remainderDivide4])); } int chinaMatchLen = intPart.size(); for (int i = 0; i < chinaMatchLen; i++) { ChinaMath chinaMath = intPart.get(i); String num = chinaMath.getNum(); String weight = chinaMath.getWeight(); if (isNotNull(num)) { //如果数字是0,则判断下一个数字或者是否是0 或者是否是节权位 //如果不是,则现在要添加一个0,否则不添加 if (num.equalsIgnoreCase(CHINEASE_DIGIT[0])) { String nextNumOrSectionWeight = intPart.get(i + 1).getNum(); if (!CHINEASE_DIGIT[0].equalsIgnoreCase(nextNumOrSectionWeight)) { integerList.add(CHINEASE_DIGIT[0]); } } else { //添加数字和权位 if (num.equalsIgnoreCase(CHINEASE_DIGIT[1]) && weight.equalsIgnoreCase(places[1]) && i == 0) { //如果十位是1,则读成拾,而不是壹拾 if (isNotNull(weight)) { integerList.add(weight); } } else { integerList.add(num); if (isNotNull(weight)) { integerList.add(weight); } } } } //节权位并且不为空,因为个位也是节权位,但没有实际值 boolean isSectionWight = chinaMath.isSectionWeight(); if (isSectionWight) { String lastNum = intPart.get(i - 1).getNum(); //如果是正常的节权位 if (isNotNull(chinaMath.getWeight())) { String nextNum = intPart.get(i + 1).getNum(); if (lastNum.equalsIgnoreCase(CHINEASE_DIGIT[0])) { if (!nextNum.equalsIgnoreCase(CHINEASE_DIGIT[0])) { //前一位是0,后一位不是0,则交换节权位和前一位的位置,例0万 -> 万0 integerList.set(integerList.size() - 1, chinaMath.getWeight()); integerList.add(CHINEASE_DIGIT[0]); } else { //前一位是0,后一位是0,需要把前一位的0去掉 integerList.remove(integerList.size() - 1); integerList.add(chinaMath.getWeight()); } } else { //前一位不是0, 直接添加节权位 integerList.add(chinaMath.getWeight()); } } else { //最后一个节权位,如果上一位是0,并且不仅有一个0,移除个位多余的0 if (lastNum.equalsIgnoreCase(CHINEASE_DIGIT[0]) && integerList.size() > 1) { integerList.remove(integerList.size() - 1); } } } } result.addAll(0, integerList); result.add(YUAN); return result; } /** * 中文数学计数 * 数字+权位 * 100中 1 佰,1位数字位,佰为权位 * 权位包括 个 拾 佰 仟 * 4位为一节,每一节有节权位如 万 亿 兆 */ private class ChinaMath { private String num; private String weight; public ChinaMath(String num, String weight) { this.num = num; this.weight = weight; } public String getNum() { return num; } public String getWeight() { return weight; } /** * 是否是节权位 * * @return 当num为空时,返回true,否则返回false */ public boolean isSectionWeight() { return (num == null || "".equalsIgnoreCase(num.trim())); } @Override public String toString() { return "ChinaMath{" + "num='" + num + '\'' + ", weight='" + weight + '\'' + '}'; } } private boolean isNotNull(String string) { return string != null && !"".equalsIgnoreCase(string); } /** * 转换小数 * * @param decimalWithPoint 小数部分(带小数点) * @return 小数部分的转换结果 */ private List transferDecimal(String decimalWithPoint) { List result = new ArrayList<>(3); if (decimalWithPoint.startsWith(DECIMAL_POINT)) { result.add(DIAN); } String decimalWithoutPoint = decimalWithPoint.replace(DECIMAL_POINT, ""); int len = decimalWithoutPoint.length(); for (int i = 0; i < len; i++) { int num = Character.getNumericValue(decimalWithoutPoint.charAt(i)); result.add(transferSingleNum(num)); } return result; } //转换单个数字 private String transferSingleNum(int num) { return CHINEASE_DIGIT[num]; } }

你可能感兴趣的:(Java)