金额转化中文算法

来源:互联网 发布:thinkphp 子域名部署 编辑:程序博客网 时间:2024/05/18 04:10

最近要项目中用到了把数字类型的金额(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<ChinaMath> 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<String> 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]

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

附完整源码:

/** * 数字转换成大写汉字 * <p> * 最大支持到千万 * <p> * 1234 5678 .90 * <p> * <p> * 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<String> 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<String> 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<String> transferInternal(String digit) {        List<String> 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<String> integerList = new ArrayList<>();        List<ChinaMath> 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<String> transferDecimal(String decimalWithPoint) {        List<String> 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];    }}
原创粉丝点击