UP | HOME

🔙回到目录 | 🔻 本网页更新于 [2026-10-25 日 00:11]

emacs-利用elisp进行大小写中文和数字的转换


有的时候,我们想快速进行数字与大小写中文的转换。

1. 将小写中文转换为阿拉伯数字

支持范围:零到九百九十九

(defun org-smart-get-chn-time-chn2num (chn-str)
  "将中文数字'零'到'九百九十九'转换为整数,支持'两'作为'二'的变体。"
  (save-match-data
    (let* ((num 0)
           (digits `(("零" . 0) ("一" . 1) ("二" . 2) ("两" . 2) ("三" . 3) ("四" . 4)
                     ("五" . 5) ("六" . 6) ("七" . 7) ("八" . 8) ("九" . 9)))
           (hundreds-pos (string-match "百" chn-str))
           (tens-pos (string-match "十" chn-str)))
      ;; 处理百位
      (when hundreds-pos
        (let* ((char-before (substring chn-str 0 hundreds-pos))
               (digit (cdr (assoc char-before digits))))
        (setq num (+ num (* digit 100)))))
      
      ;; 处理十位
      (when tens-pos
        (let* ((char-before (if hundreds-pos
                                (substring chn-str (1+ hundreds-pos) tens-pos)
                              (substring chn-str 0 tens-pos)))
               (digit (cdr (assoc char-before digits))))
          (cond
           ;; "十" 或 "一十" 的情况
           ((or (string= "" char-before) (string= "一" char-before))
            (setq num (+ num 10)))
           ;; "零" 在十位前,例如 "一百零五" 中的 "零"
           ((string= "零" char-before))
            ;; "零" 本身不增加数值,但会影响个位的解析,此处无需额外操作
           (t
            (setq num (+ num (* (or digit 1) 10)))))))
      
      ;; 处理个位
      (let* ((last-char (substring chn-str -1))
             (last-digit (cdr (assoc last-char digits))))
        (when (and last-digit (not (or (string= last-char "百") (string= last-char "十"))))
          ;; 如果 "零" 出现在末尾,例如 "一百一十",last-char会是"十",所以这里的判断是有效的
          (if (and tens-pos (= 0 (length (substring chn-str (1+ tens-pos)))))
              ;; 类似于 "二十", "三十" 这样的整十数,个位不再处理
              ()
            (setq num (+ num last-digit)))))
      
      ;; 处理特殊情况 "十"
      (if (string= "十" chn-str)
          10
        num))))

2. 将阿拉伯数字转化为大写/小写中文

支持范围: 0.01 到 99999999.99 之间的数字,小数最多2位数。
交互式调用 num2chn 默认会提示输入数字并转换为小写中文;如果用 C-u 的前缀调用,则转换为会计的大写中文。
在elisp 中调用时统一转换为小写中文。

(defconst num2chn--lower-digits
  '("零" "一" "二" "三" "四" "五" "六" "七" "八" "九"))

(defconst num2chn--upper-digits
  '("零" "壹" "贰" "叁" "肆" "伍" "陆" "柒" "捌" "玖"))

;; 依次为:千 百 十 个 万
(defconst num2chn--lower-units '("千" "百" "十" "" "万"))
(defconst num2chn--upper-units '("仟" "佰" "拾" "" "万"))

(defun num2chn--section (n digits units strip)
  "把 0-9999 的整数 N 转成中文。
DIGITS 为 0-9 的中文数字表,UNITS 为 (千 百 十 \"\")。
STRIP 非 nil 且结果以“一十/壹拾”开头时,省略开头的数字。"
  (let ((result "")
        (pending-zero nil))
    (dolist (pair '((1000 . 0) (100 . 1) (10 . 2) (1 . 3)))
      (let* ((place (car pair))
             (idx (cdr pair))
             (digit (floor n place)))
        (setq n (% n place))
        (cond
         ((zerop digit)
          ;; 只有后面还有非零数字时才补“零”
          (when (and (> n 0) (> (length result) 0))
            (setq pending-zero t)))
         (t
          (when pending-zero
            (setq result (concat result (nth 0 digits))))
          (setq pending-zero nil)
          (setq result (concat result (nth digit digits) (nth idx units)))))))
    (if (and strip
             (>= (length result) 2)
             (string= (substring result 0 1) (nth 1 digits))
             (string= (substring result 1 2) (nth 2 units)))
        (substring result 1)
      result)))

(defun num2chn--integer (n digits units strip)
  "把 0-99999999 的整数 N 转成中文。
DIGITS、UNITS 含义同 `num2chn--section';STRIP 只作用于最高位那一段。"
  (cond
   ((zerop n)
    (nth 0 digits))
   ((> n 9999)
    (let* ((high (floor n 10000))
           (low (mod n 10000))
           (result (concat (num2chn--section high digits units strip)
                           (nth 4 units))))
      (if (zerop low)
          result
        (concat result
                (if (< low 1000) (nth 0 digits) "")
                (num2chn--section low digits units nil)))))
   (t
    (num2chn--section n digits units strip))))

(defun num2chn (num &optional uppercase clipboard)
  "把 0.01 到 99999999.99 之间的数字转换成中文。
NUM 可以是数字或数字字符串,超过两位小数时四舍五入。

UPPERCASE 非 nil 时返回金额大写(壹元贰角叁分),否则返回普通读数
(一点二三)。CLIPBOARD 非 nil 时把结果复制到剪贴板。

交互调用时读取一个数字,并把结果复制到剪贴板;带前缀参数(C-u)
时返回金额大写。作为函数调用时不碰剪贴板。

  (num2chn 1.23)       => \"一点二三\"
  (num2chn 1.23 t)     => \"壹元贰角叁分\"
  (num2chn 1.23 nil t) => 返回 \"一点二三\",同时复制到剪贴板"
  (interactive
   (list (read-number "数字 (0.01~99999999.99): ") current-prefix-arg t))
  (let* ((num (if (numberp num) num (string-to-number num)))
         (cents (round (* num 100))))
    (unless (<= 1 cents 9999999999)
      (error "请输入 0.01 到 99999999.99 之间的数字"))
    (let* ((int-part (floor cents 100))
           (frac (mod cents 100))
           (jiao (floor frac 10))
           (fen (mod frac 10))
           (result (if uppercase
                       (num2chn--currency int-part jiao fen)
                     (num2chn--plain int-part jiao fen))))
      (when clipboard
        (kill-new result)
        (message "已复制到剪贴板:%s" result))
      result)))

(defun num2chn--plain (int-part jiao fen)
  "普通读法:整数部分 + “点” + 小数位。"
  (let ((digits num2chn--lower-digits)
        (units num2chn--lower-units))
    (concat (num2chn--integer int-part digits units t)
            (cond
             ((and (zerop jiao) (zerop fen)) "")
             ((zerop jiao) (concat "点" (nth 0 digits) (nth fen digits)))
             ((zerop fen) (concat "点" (nth jiao digits)))
             (t (concat "点" (nth jiao digits) (nth fen digits)))))))

(defun num2chn--currency (int-part jiao fen)
  "金额大写:元、角、分;无角分时以“整”结尾。"
  (let* ((digits num2chn--upper-digits)
         (units num2chn--upper-units)
         (int-str (if (> int-part 0)
                      (concat (num2chn--integer int-part digits units nil) "圆")
                    "")))
    (cond
     ((and (zerop jiao) (zerop fen))
      (concat int-str "整"))
     (t
      (concat int-str
              (if (and (> int-part 0) (zerop jiao) (> fen 0))
                  (nth 0 digits)
                "")
              (if (> jiao 0) (concat (nth jiao digits) "角") "")
              (if (> fen 0) (concat (nth fen digits) "分") ""))))))

3. 方便在正则中调用的别名

这样一来就可以用 \,(str2num \1) 在正则中将匹配到的数字结果转换为数字进行计算后替换回去了。

(defalias 'num2str 'number-to-string)
(defalias 'str2num 'string-to-number)

© Published by Emacs 32.0.50 (Org mode 10.0-pre) | RSS 评论