一、业务背景
最近在写一个算法,数字金额转换成中文大写。
这个算法看起来不难,我又很懒,让AI帮我写吧。
算法写出来了,测试一下。
1234567890.12 这一串复杂的数字,转换后:壹拾贰亿叁仟肆佰伍拾陆万柒仟捌佰玖拾元壹角贰分,核对一下,结果是对的。
十分欣喜,AI真是懒人的福音呀!于是就将这个算法应用到了项目里。
在一次测试中,偶尔输入1800000元,转换后:壹佰捌拾。
这么 low 的 bug 也有!!!
人工修正后,1800000成功转换为壹佰捌拾万元整。总以为这下代码变得完美了。
但是,在输入1000001.00时,转换又出错了,再次变成壹佰零壹元整。
多次对代码修修补补,可依旧有转换出错的数字,AI也靠不住呀!
怎么办呢?
二、任务分解
写算法也要“分而治之”。
一步干不完的事情,就分两步,两步不行再分,一直分到可以解决。
苦思冥想之后,把一个金额分为小数部分和整数部分,小数部分和整数部分分别处理,最后再合并。
具体步骤如下:
第一步,将整数部分和小数部分进行分离。
第二步,整数部分的处理。
1、对整数进行数组处理;
2、分组后的处理;
3、合并分组处理结果。
第三步,小数部分的处理。
最后,合并整数部分和小数部分的处理结果。
算法很快写出来了,稍微调整,再无差错,目前支持上亿金额的转换。
来看看这个算法吧。如果你用得到,可以拿来就用。
三、算法实现
/** * 数字金额大写转换 * @author 程就人生 * @date 2024年12月2日 * @Description * */public class MoneyToChinese { // 数字大写 private static final char[] chineseNumerals = {'零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'}; // 单位 private static final String[] unit = {"", "拾", "佰", "仟"}; // 分组单位 private static final String[] groupUnit = {"", "万", "亿", "万亿"};
// 转换方法 public static String convertToChineseUppercase(Object obj) { Double amount = Double.valueOf(obj.toString()); // 将金额转为字符串,保证保留两位小数 String strAmount = String.format("%.2f", amount); // 拆分整数部分和小数部分 String[] parts = strAmount.split("\\."); String strInteger = null; // 整数部分是否为0 boolean integerIsZero = false; if(parts[0].equals("0")){ integerIsZero = true; } // 整数部分不等于0时才需要处理 if(!integerIsZero){ strInteger = integerConvert(Long.valueOf(parts[0])); } String strDecimal = decimalConvert(parts[1], integerIsZero); // 如果都为0时 if(strInteger == null && strDecimal.isEmpty()){ return "零元"; } // 小数部分为0时 if(strDecimal.isEmpty()){ return strInteger.concat("元整"); // 没有整数部分时 }else if(strInteger == null){ return strDecimal; }else{ return strInteger.concat("元").concat(strDecimal); } }
/** * 小数部分处理 * @param decimalPart * @return */ public static String decimalConvert(String decimalPart, boolean integerIsZero){ // 处理小数部分 StringBuilder decimalResult = new StringBuilder(); if (!decimalPart.equals("00")) { // 如果小数部分不为00,则转换 //decimalResult.append("点"); if (decimalPart.charAt(0) != '0') { decimalResult.append(chineseNumerals[decimalPart.charAt(0) - '0']).append("角"); }else if(!integerIsZero){ decimalResult.append("零"); } if (decimalPart.charAt(1) != '0') { decimalResult.append(chineseNumerals[decimalPart.charAt(1) - '0']).append("分"); } } return decimalResult.toString(); }
/** * 整数部分转换 * @param number * @return */ public static String integerConvert(long number) { StringBuilder result = new StringBuilder(); String numStr = Long.toString(number); int length = numStr.length(); // 分组处理每四位 int groupCount = (length + 3) / 4; // 计算需要处理的组数 boolean lastGroupZero = false; // 标记上一组是否全为零 for (int i = 0; i < groupCount; i++) { int start = length - 4 * (i + 1); // 当前组开始位置 int end = length - 4 * i; // 当前组结束位置 if (start < 0) { start = 0; } String group = numStr.substring(start, end); String groupChinese = convertGroup(group); if (!"零".equals(groupChinese)) { // 当前组不全为零时添加单位 result.insert(0, groupChinese + groupUnit[i]); lastGroupZero = false; } else if (!lastGroupZero && i > 0) { // 当前组全为零且不是第一组 result.insert(0, "零"); lastGroupZero = true; } } // 删除结果中多余的零 while (result.indexOf("零零") != -1) { result = new StringBuilder(result.toString().replace("零零", "零")); } if (result.lastIndexOf("零") == result.length() - 1) { result.deleteCharAt(result.length() - 1); } if (result.indexOf("零") == 0) { result.deleteCharAt(0); } return result.toString(); }
/** * 对数据进行分组处理 * @param group * @return */ private static String convertGroup(String group) { StringBuilder groupChinese = new StringBuilder(); int len = group.length(); boolean lastNumZero = false; // 记录上一个数字是否为零,用于控制零的输出 for (int i = 0; i < len; i++) { int num = group.charAt(i) - '0'; if (num != 0) { if (lastNumZero) { groupChinese.append('零'); } groupChinese.append(chineseNumerals[num]).append(unit[len - i - 1]); lastNumZero = false; } else { lastNumZero = true; } } return groupChinese.toString(); }
// 测试 public static void main(String[] args) { System.out.println(convertToChineseUppercase(1234567890.12)); // 壹拾贰亿叁仟肆佰伍拾陆万柒仟捌佰玖拾元壹角贰分 System.out.println(convertToChineseUppercase(0.50)); // 伍角 System.out.println(convertToChineseUppercase(0.05)); // 伍分 System.out.println(convertToChineseUppercase(1234.00)); // 壹仟贰佰叁拾肆元整 System.out.println(convertToChineseUppercase(1008.00)); // 壹仟零捌元整 System.out.println(convertToChineseUppercase(108.00)); // 壹佰零捌元整 System.out.println(convertToChineseUppercase(18.00)); // 壹拾捌元整 System.out.println(convertToChineseUppercase(180.00)); // 壹佰捌拾元整 System.out.println(convertToChineseUppercase(1800.00)); // 壹仟捌佰元整 System.out.println(convertToChineseUppercase(1080000.00)); // 壹佰零捌万元整 System.out.println(convertToChineseUppercase(1880000.00)); // 壹佰捌拾捌万元整 System.out.println(convertToChineseUppercase(1800000.00)); // 壹佰捌拾万元整 System.out.println(convertToChineseUppercase(18800000.00)); // 壹仟捌佰捌拾万元整 System.out.println(convertToChineseUppercase(18000000.00)); // 壹仟捌佰万元整 System.out.println(convertToChineseUppercase(10000.00)); // 壹万元整 System.out.println(convertToChineseUppercase(100001.00)); // 壹拾万零壹元整 System.out.println(convertToChineseUppercase(100001.01)); // 壹拾万零壹元零壹分 System.out.println(convertToChineseUppercase(10000000001.00)); // 壹佰亿万零壹元整 }}
工具类说明:
1、常量定义
chineseNumerals:定义了数字(0~9)的中文表示。
unit:定义了四位分组内的权位单位(如拾、佰、仟)。
groupUnit:定义了每四位为一组的分组单位(如万、亿、万亿)。
2、方法
2.1、convertToChineseUppercase():将输入金额转换为 Double 类型
格式化金额为保留两位小数的字符串。
将整数部分和小数部分分开处理。
返回结果格式为:
整数 + “元整”(若小数为0)。
整数 + “元” + 小数部分。
小数部分(若整数为0)。
2.2、decimalConvert():小数部分转换。
若小数为“00”,返回空字符串。
处理角位和分位,按照规则拼接大写金额:
非零则转换为中文大写加单位(如“壹角”)。
零分处理为“零”,仅在整数不为零时追加。
返回最终的小数部分结果。
2.3、integerConvert():整数部分转换。
将整数按四位一组分割。
调用 convertGroup 方法处理每组的中文转换。
将每组结果拼接成完整的中文大写金额。
2.4、convertGroup():分组转换。
四位以内的数字字符串(如“1234”)。
遍历每一位数字:
非零时转换为中文大写数字加权位。
零则记录,避免重复输出。
拼接成当前组的中文表示。
在这个算法中,我也曾经想过,18.00元转换后是壹拾捌元整,还是拾捌元整?拾捌元整读起来更顺畅一点。
查了以下资料,转换成壹拾捌元整比较合适。为什么,主要是为了防篡改。
有了java版本的,还需要js版本的,也一同测试下。
四、转换成js版本
// 数字对应大写const chineseNumerals = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];// 单位const unit = ['', '拾', '佰', '仟'];// 分组单位const groupUnit = ['', '万', '亿', '万亿'];// function convertToChineseUppercase(amount) {// 将金额转为字符串,保证保留两位小数const strAmount = parseFloat(amount).toFixed(2);// 拆分整数部分和小数部分const parts = strAmount.split('.');let strInteger = null;let integerIsZero = false;if (parts[0] === "0") {integerIsZero = true;}if (!integerIsZero) {strInteger = integerConvert(parseInt(parts[0], 10));}const strDecimal = decimalConvert(parts[1], integerIsZero); // 如果都为0时 if(strInteger == null && strDecimal === ''){ return "零元"; }if (strDecimal === '') {return strInteger + "元整";} else if (strInteger === null) {return strDecimal;} else {return strInteger + "元" + strDecimal;}}// 小数部分处理function decimalConvert(decimalPart, integerIsZero) {let decimalResult = '';if (decimalPart !== "00") {if (decimalPart[0] !== '0') {decimalResult += chineseNumerals[decimalPart[0]] + "角";} else if (!integerIsZero) {decimalResult += "零";}if (decimalPart[1] !== '0') {decimalResult += chineseNumerals[decimalPart[1]] + "分";}}return decimalResult;}// 整数部分处理function integerConvert(number) {let result = '';const numStr = number.toString();const length = numStr.length;const groupCount = Math.ceil(length / 4); // 计算需要处理的组数let lastGroupZero = false;for (let i = 0; i < groupCount; i++) {const start = Math.max(0, length - 4 * (i + 1));const end = length - 4 * i;const group = numStr.substring(start, end);const groupChinese = convertGroup(group);if (groupChinese !== "零") {result = groupChinese + groupUnit[i] + result;lastGroupZero = false;} else if (!lastGroupZero && i > 0) {result = "零" + result;lastGroupZero = true;}}result = result.replace(/零+/g, "零");if (result.endsWith("零")) {result = result.slice(0, -1);}if (result.startsWith("零")) {result = result.slice(1);}return result;}// 整数部分分组function convertGroup(group) {let groupChinese = '';const len = group.length;let lastNumZero = false;for (let i = 0; i < len; i++) {const num = parseInt(group[i], 10);if (num !== 0) {if (lastNumZero) {groupChinese += '零';}groupChinese += chineseNumerals[num] + unit[len - i - 1];lastNumZero = false;} else {lastNumZero = true;}}return groupChinese;}
五、最后总结
这个算法符合常见的金额大写规则。代码清晰,模块化分工明确。有较强的扩展性(支持亿、万亿单位)。自夸一下,哈哈。
当然还可以再优化,比如对零的处理比较重复,建议集中简化处理逻辑。groupUnit 中可添加更高单位(如“兆”),以支持更大金额。
这个算法或许并不完美,但是已经符合我的需要,就写这么多吧。
领取专属 10元无门槛券
私享最新 技术干货