紫蝶冰澜 发表于 2024-10-29 22:54

将数字转换为汉字的算法

本帖最后由 紫蝶冰澜 于 2024-10-30 12:20 编辑


# 代码思路
1. 使用了BigInteger和BigDecimal,这样可以省去对字符串是否为数字的判断,也方便了后续直接使用字符串进行处理
2. 先找到小数点,将数字分为两个部分,整数部分还可以确定符号性,确定后,将整数部分改为正数,方便后续处理,这也是类的三个字段的由来
3.小数部分很好处理,直接转换就行
4.整数部分每4位分为一组
5.每组的4位数在转换时,依次转换,遇到0时,不需要跟上单位
6.我预留了处理一十几的读数,这样就能忽略最高位的一
7.每组的4位数内,高位的“零”需要保留一个,因为分割数组时不会出现最高位是0的情况,多亏了BigInteger和BigDecimal的toString方法
8.中间的两个0可以连读为一个0,末尾的连续0就不读了
9.如果整组4个数都是0,需要按照最高位是0的情况保留一个0
10.已经分好组的单位,可以发现规律,先是“万”,然后是“亿”,剩下的就靠循环就能补足“亿”即可

# 代码
java5以上都支持,欢迎测试,甚至提出优化建议,一起交流,这个功能可以服务于文字转语音时,对数字读取的准确性,使得语音不是那么生硬
```java
import java.math.BigDecimal;
import java.math.BigInteger;

public class Main {
    public static void main(String[] args) {
      for (int i = 0; i <= 31; i++) {
            toStr(i);
      }
      for (int i = 50998; i <= 51010; i++) {
            toStr(i);
      }
      for (int i = 99998; i <= 100002; i++) {
            toStr(i);
      }

      toStr(-1200001234);

      String s = "-0.1234567890123";
      System.out.println(s + "=>" + new CN_NumString(new BigDecimal(s)).parse());

      s = "0.0001";
      System.out.println(s + "=>" + new CN_NumString(new BigDecimal(s)).parse());

      s = "-1002340005600.1000";
      System.out.println(s + "=>" + new CN_NumString(new BigDecimal(s)).parse());
    }

    public static void toStr(int i) {
      System.out.println(i + "=>" + new CN_NumString(new BigDecimal(Integer.toString(i))).parse());
    }
}

class CN_NumString {
    private static String[] CN_NUMBER = {"零", "一", "二", "三", "四", "五", "六", "七", "八", "九"};
    private static String[] CN_CONST = {"负", "点"};
    private static String[] CN_UNIT_1 = {"", "十", "百", "千"};
    private static String[] CN_UNIT_2 = {"", "万", "亿"};

    private boolean isNeg;
    private String iStr;
    private String fStr;

    public CN_NumString(BigDecimal b) {
      String s = b.toString();
      CN_NumString n = new CN_NumString(s);
      this.isNeg = n.isNeg;
      this.iStr = n.iStr;
      this.fStr = n.fStr;
    }

    public CN_NumString(BigInteger b) {
      String s = b.toString();
      CN_NumString n = new CN_NumString(s);
      this.isNeg = n.isNeg;
      this.iStr = n.iStr;
      this.fStr = "";
    }

    private CN_NumString(String string) {
      String[] s = string.split("\\.");
      this.iStr = s;
      this.isNeg = this.iStr.charAt(0) == '-';
      if (this.isNeg) this.iStr = this.iStr.substring(1);
      this.fStr = s.length == 2 ? s : "";
      if (this.fStr.isEmpty() && "0".equals(this.iStr)) this.isNeg = false;
    }

    public String parse() {
      StringBuilder sb = new StringBuilder();
      if (isNeg) sb.append(CN_CONST);
      String s = _0(parseInteger());
      sb.append(s.isEmpty() ? CN_NUMBER : s);
      if (this.fStr != null && !this.fStr.isEmpty()) sb.append(CN_CONST).append(parseFloat());
      return sb.toString();
    }

    private String parseInteger() {
      StringBuilder sb = new StringBuilder();
      String[] ss = _4(this.iStr);
      for (int i = 0; i < ss.length; i++) {
            int g = ss.length - 1 - i;
            if ("0000".equals(ss)) {
                sb.append(CN_NUMBER);
            } else {
                sb.append(_0(parse_4(ss)))
                        .append(parseIntegerUnit(g));
            }
      }
      return sb.toString();
    }

    private String parseIntegerUnit(int g) {
      StringBuilder sb = new StringBuilder();
      if (g == 0) return CN_UNIT_2;
      if (g % 2 == 1) sb.append(CN_UNIT_2);
      for (int i = 0; i < g - 1; i = i + 2) sb.append(CN_UNIT_2);
      return sb.toString();
    }

    private String parse_4(String n) {
      StringBuilder sb = new StringBuilder();
      for (int i = 0; i < n.length(); i++) {
            String s = CN_NUMBER;
            sb.append(s);
            if (!CN_NUMBER.equals(s)) {
                String u = CN_UNIT_1;
                sb.append(u);
            }
      }
      return _10(sb.toString());
      //return sb.toString();
    }

    private String _10(String s) {
      return (s.length() == 2 || s.length() == 3)
                && s.charAt(0) == CN_NUMBER.charAt(0)
                && s.charAt(1) == CN_UNIT_1.charAt(0)
                ? s.substring(1) : s;
    }

    private String _0(String s) {
      return s.replace(CN_NUMBER + CN_NUMBER + CN_NUMBER, CN_NUMBER)
                .replace(CN_NUMBER + CN_NUMBER, CN_NUMBER)
                .replaceAll(CN_NUMBER + "+$", "");
    }

    private String[] _4(String n) {
      int l = n.length();
      int g = (l + 3) / 4;
      String[] r = new String;
      for (int i = 0; i < g; i++) {
            int e = l - i * 4;
            int s = Math.max(0, e - 4);
            r = n.substring(s, e);
      }
      return r;
    }

    private String parseFloat() {
      StringBuilder sb = new StringBuilder();
      for (int i = 0; i < fStr.length(); i++) {
            sb.append(CN_NUMBER);
      }
      return sb.toString();
    }
}
```

# 部分测试结果

```
0=>零
1=>一
2=>二
3=>三
4=>四
5=>五
6=>六
7=>七
8=>八
9=>九
10=>十
11=>十一
12=>十二
13=>十三
14=>十四
15=>十五
16=>十六
17=>十七
18=>十八
19=>十九
20=>二十
21=>二十一
22=>二十二
23=>二十三
24=>二十四
25=>二十五
26=>二十六
27=>二十七
28=>二十八
29=>二十九
30=>三十
31=>三十一
50998=>五万零九百九十八
50999=>五万零九百九十九
51000=>五万一千
51001=>五万一千零一
51002=>五万一千零二
51003=>五万一千零三
51004=>五万一千零四
51005=>五万一千零五
51006=>五万一千零六
51007=>五万一千零七
51008=>五万一千零八
51009=>五万一千零九
51010=>五万一千零一十
99998=>九万九千九百九十八
99999=>九万九千九百九十九
100000=>十万
100001=>十万零一
100002=>十万零二
-1200001234=>负十二亿零一千二百三十四
-0.1234567890123=>负零点一二三四五六七八九零一二三
0.0001=>零点零零零一
-1002340005600.1000=>负一万亿零二十三亿四千万五千六百点一零零零
```

Carinx 发表于 2024-10-30 09:31

凑巧了这不是,前段时间刚好因为需求实现了数字时间转中文时间的工具方法,也分享一下给大伙们哈哈哈


    private static final String[] CHINESE_NUMBERS = {"零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十"};
    private static final String[] CHINESE_MONTHS = {"", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二"};

   /**
   * 返回指定格式的时间字符串
   * @AuThor Carinx
   * @date 2021/12/22 16:29
   * @Param value 原时间字符串
   * @param pattern 返回时间格式
   * @Return java.lang.String 没有匹配到相关时间格式则返回原字符串
   *
   **/
    public static String parseDate(String value, String pattern) {

      if (value == null || "".equals(value)) {
            return null;
      }

      DateFormat dateFormat;

      //兼容高精度情况
      if (value.matches("^{1}{3}-(?:0|1)-(?:0|\\d|3)\\s+(?:\\d|2):\\d:\\d(?:\\.\\d+)?$")){
            dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
      }
      else if (value.matches("^{1}{3}-(?:0|1)-(?:0|\\d|3)$")) {
            dateFormat = new SimpleDateFormat("yyyy-MM-dd");
      }
      else if (value.matches("^{1}{3}(?:0|1)(?:0|\\d|3)$")) {
            dateFormat = new SimpleDateFormat("yyyyMMdd");
      }
      else if (value.matches("^{1}{3}/(?:0|1)/(?:0|\\d|3)$")) {
            dateFormat = new SimpleDateFormat("yyyy/MM/dd");
      }
      else if (value.matches("^{1}{3}年(?:0|1)月(?:0|\\d|3)日$")) {
            dateFormat = new SimpleDateFormat("yyyy年MM月dd日");
      }
      else if (value.matches("^{1}{3}\\.(?:0|1)\\.(?:0|\\d|3)$")) {
            dateFormat = new SimpleDateFormat("yyyy.MM.dd");
      }
      else if (value.matches("^{1}{3}\\.(?:0|1)$")) {
            dateFormat = new SimpleDateFormat("yyyy.MM");
      }
      else if (value.matches("^(?:\\d|2):\\d:\\d(?:\\.\\d{3})?$")) {
            dateFormat = new SimpleDateFormat("HH:mm:ss");
      }
      //10位数字和13位数字,其中,10位数字表示秒级时间戳,13位数字表示毫秒级时间戳
      else if (value.matches("^\\d{10}|\\d{13}$")) {
            return new SimpleDateFormat(pattern)
                  .format(
                            new Date(Long.parseLong(value))
                  );
      }
      else {
            //没有匹配到相关时间格式则返回原字符串
            return value;
      }

      try {
            return new SimpleDateFormat(pattern).format(dateFormat.parse(value));
      } catch (ParseException e) {
            e.printStackTrace();
            throw new Exception("格式化日期失败!");
      }
    }


    /**
   * 转换日期为中文日期
   * @author Carinx
   * @date 2024/10/21 15:01
   * @param localDate待转换日期
   * @param ChinesePattern转换输出的中文日期格式映射值:1年月日, 2年月, 3年
   * @return java.lang.String 中文格式日期
   *
   **/
    public static String convertToChineseDate(LocalDate localDate, int ChinesePattern) {

      int year = localDate.getYear();
      int month = localDate.getMonthValue();
      int day = localDate.getDayOfMonth();

      StringBuilder result = new StringBuilder();

      // 转换年份
      if (ChinesePattern == 1 || ChinesePattern == 2 || ChinesePattern == 3) {

            for (char c : String.valueOf(year).toCharArray()) {
                result.append(CHINESE_NUMBERS);
            }
            result.append("年");

      }

      // 转换月份
      if (ChinesePattern == 1 || ChinesePattern == 2) {
            result.append(CHINESE_MONTHS).append("月");
      }

      // 转换日期
      if (ChinesePattern == 1) {
            if (day <= 10) {
                result.append("初").append(CHINESE_NUMBERS);
            } else if (day < 20) {
                result.append("十").append(CHINESE_NUMBERS);
            } else if (day == 20) {
                result.append("二十");
            } else if (day < 30) {
                result.append("二十").append(CHINESE_NUMBERS);
            } else if (day == 30) {
                result.append("三十");
            } else {
                result.append("三十").append(CHINESE_NUMBERS);
            }
            result.append("日");
      }

      return result.toString();
    }

fhlfxtd 发表于 2024-10-30 07:21

借鉴学习了

arctan1 发表于 2024-10-30 07:22

密码学,这是密码学

q27557923 发表于 2024-10-30 15:54

大佬,要是改成汉字大写的那种字可以吗?

tengfei123456 发表于 2024-10-30 16:01

学习学习

dongshan1986 发表于 2024-10-30 16:48

这种的,用AI工具生成很方便的

telnetpig 发表于 2024-10-30 17:20

支持一下原创

upfour 发表于 2024-10-30 21:16

可以再完善一下,能转换为大写金额的

wasd71 发表于 2024-10-31 20:00

大单位存一个循环列表不是更好吗
页: [1] 2
查看完整版本: 将数字转换为汉字的算法