金额转化中文算法
来源:互联网 发布: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])); }
过滤+转换
第三步:过滤+转换
这一步是最麻烦的,我们要以第二步的结果为基础,再根据中文的金额读写特点进行加工。
例如:
- 金额中间有多个0,只读一个零
- 金额末尾有多个0,都不读
- 10要读拾,不能读成壹拾
- 文章开头提到,节权位前的零不能省
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]; }}
阅读全文
0 0
- 金额转化中文算法
- 中文大写数字/金额转化
- js转化金额为中文
- Java:将金额转化成中文
- SQL Server中文大写金额转化函数
- SQL Server中文大写金额转化函数
- 数字金额转化为中文大写
- JavaScript 动态将数字金额转化为中文大写金额
- JAVA应用: 浮点数转化为大写中文金额
- java代码中将金额阿拉伯数字转化为中文大写
- js阿拉伯数字转化为中文(非金额)
- java实现将整数转化为中文大写金额
- PHP将数字金额转化为中文人民币大写
- 1 金额转化(中文大写转化为数字)(郭勇延)
- 金额转化java代码
- 金额大写转化
- python 数字金额转化
- 金额与数字转化
- Others13_在黑市里,苹果iPhone是这样被解锁的
- vue学习笔记6 Vue.directive自定义指令
- Autofac官方文档(十六)【在启动时运行代码】
- DockerFile方式构建docker镜像
- JAVA基础-类4-静态变量,常量和方法
- 金额转化中文算法
- Autofac官方文档(十七)【配置】
- MIPI接口介绍
- 基于etcd的分布式定时任务框架
- 自定义ViewPager+WebView跳转
- STM32功耗解析
- 当电脑有多个版本的CuDNN的时候caffe该如何“抉择”?
- Autofac官方文档(十八)【JSON/XML 配置】
- HTML5 语义化