news 2026/9/3 6:53:03

【需求设计】小写数字金额转换成中文大写金额

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【需求设计】小写数字金额转换成中文大写金额

amount2cn 设计文档

小写数字金额 → 中文大写金额转换.

  • 原文参考:https://blog.csdn.net/liudglink/article/details/106820490
  • 本文件为设计说明,代码实现请访问 gitee仓库

[零]用法与整体流水线

多文件程序(同属package main),无外部依赖,由go.mod管理,直接运行:

go run *.go1234.56# 命令行模式:逐个参数转换后打印go run *.go# 无参数:跑 main 中内置的自测用例DEBUG_MAT=1go run *.go1234.56# 打印 materialize 的下标序列到 stderrgo run *.go-d1234.56# 同上,-d 可出现在参数列表任意位置

对外唯一入口arabToChinese(string) string,内部是严格单向流水线:

步骤函数职责
STEP-1trimCaspAll清洗:去掉空格 / Tab / 千分位逗号
STEP-2isZeroYuan零值字面量快捷返回(“零圆整”)
STEP-3parseAmountDFA 校验与切分;失败则直接返回msgCodes[EC_MINUS_POS~EC_TOO_LONG]
STEP-4convertIntPart/convertDecPart只产出[]int下标序列,不拼字符串
STEP-5materialize下标 → 字符串,全文件唯一的字符输出口

非法输入返回以[ERR]开头的中文提示串,调用方可用
strings.HasPrefix(out, "[ERR]")判定转换失败.

设计上"解析"与"转换"两阶段解耦:

  • DFA 阶段只负责"格式合法性校验"与"切分 负号 / 整数部分 / 小数部分",
    不关心具体的中文怎么写;返回码 0 成功,1~5 为错误.
  • 转换阶段只做纯数值 → 中文的映射,无状态机,易单测,易维护.

核心改造点(详见 [三]):所有输出字符集中在digits/units/aux三张
map[int]string中(键空间互不重叠;另有第四张msgCodes为返回码表,
不参与字符输出),转换逻辑只向[]int下标序列 append 下标,materialize()
统一把下标映射回字符并应用"壹拾"简写规则,实现字符定义与逻辑彻底解耦.


[一]DFA(确定性有限状态机)形式化设计

1. 状态集合 Q

Q = { BEGIN, MINUS, ZEROPRE, INTEGER, ERROR }
状态含义
BEGIN起始态
MINUS已读负号-(仅在开头合法)
ZEROPRE前缀零模式(0.xx-0.xx),用于禁止0XXX这类前置零
INTEGER整数数字状态(进入后允许任意 0-9 连续)
ERROR拒绝态(对应返回码EC_MINUS_POS~EC_TOO_LONG)

实现说明:ERROR只是形式化描述----代码从不"转移"进ERROR,一旦判定
非法就立即return,故state枚举里的errState从未被赋值(死常量,
保留仅为与状态图对齐,见 [五]).

2. 字母表 Σ

Σ = { '-', '.', '0', D(1-9), OTHER } D = '1'..'9',OTHER = 其他任意字符(非法)

3. 状态转移函数 δ(current × input → next)

当前\输入'-''.''0''1'-'9'OTHER
BEGINMINUS无整数①ZEROPREINTEGERERROR(4)
MINUSERR(1)无整数②ZEROPREINTEGERERROR(4)
ZEROPREERR(1)切小数③ERROR(3)INTEGERERROR(4)
INTEGERERR(1)切小数③INTEGERINTEGERERROR(4)
  • BEGIN.合法,表示无整数部分(如.19)
  • MINUS.合法,表示无整数部分(如-.12)
  • .最多出现一次,第二次出现 →ERROR(2)

实现说明:两个与状态图不完全对应的细节----

  • .之后状态一律置为INTEGER,等价于BEGIN/MINUS/ZEROPRE/INTEGER
    四态在此合并,因为后续只需靠seenDot决定数字写进哪个缓冲区;
  • 小数点重复并非由状态机发现,而是parseAmount开头用dotCount
    预检拦截(码 2),所以转移表里那格实际不会走到.

4. 初态 / 终态(接受态)/ 拒绝态

  • 初态:BEGIN
  • 接受态:BEGIN, MINUS, ZEROPRE, INTEGER
    代码在扫描结束后并不校验终态,故这四个态全部算合法,
    包括"一个数字都没有"的BEGINMINUS.退化输入(空串,
    -等)即由此产生,实际输出见 [四].
  • 拒绝态:ERROR(判定即立即返回,不参与终态校验)

5. 返回码体系(parseResult.ErrCode / msgCodes 键)

代码中应统一使用a2c_tables.go中定义的EC_*具名常量,避免裸写魔法数字。

常量名含义
0EC_OK成功(此时直接返回转换结果,不会去查msgCodes,故msgCodes[EC_OK]虽定义为""却从未被读取,见 [五])
1EC_MINUS_POS负号-位置错误(不在开头)
2EC_DOT_MULTI小数点.出现多次
3EC_LEAD_ZERO前置零违规(如0XXX,仅允许0.xx/-0.xx)
4EC_BAD_CHAR非法字符(非 0-9 /-/.)
5EC_TOO_LONG数值过大,整数部分位数超过units表支持的最大权位(见 [四])

成功用EC_OK(0) 表示,非 0 即错误;错误提示串集中存于msgCodes[EC_MINUS_POS~EC_TOO_LONG].

6. DFA 状态转移图(flowchart)

- (仅开头)

. (无整数①)

0

1-9

OTHER

. (无整数②)

0

1-9

- / OTHER

. (切小数③)

1-9

0 (EC_LEAD_ZERO:前置零)

- / OTHER

0 / 1-9 (自循环)

- / OTHER

判定即 return,返回 msgCodes[EC_MINUS_POS~EC_TOO_LONG]

BEGIN
(起始态)

MINUS
(已读负号)

ZEROPRE
(前缀零模式)

INTEGER
(整数数字态)

ERROR
(拒绝态,EC_MINUS_POS~EC_TOO_LONG)

(终止)

注:.之后状态统一置为INTEGER(四态合并),依靠seenDot决定数字
写入整数还是小数缓冲区;小数点重复由parseAmount开头dotCount预检拦截
(码 2),故转移表中.重复的情形实际不会走到.

其它非法输入(负号不在开头 / 小数点多次 / 非法字符)→ERROR(码 1 / 2 / 4)

7. 合法串示例

"1001000088080.01" "-0.20" ".19" "-.12" "80,493,071,641,101.12" (逗号在 DFA 之前由 trimCaspAll 清洗掉)

[二]转换阶段(纯映射,无状态机)

  • 负号→ 序列头部插入 “负”;不影响后续任何数值规则.

  • 整数部分(convertIntPart):从左到右(高位 → 低位)逐位处理.
    w = n - i= 剩余长度(含当前位),即原文 C 实现的CU_INT[w]索引.
    权位单位取units[UNIT_BASE + w]:

    • w%4 == 1→ 级末单位:w=1圆 /5萬 /9億 /13兆 /17京 /21垓 /25秭 …

    • 其余 → 拾 / 佰 / 仟(分别对应w%4 = 2 / 3 / 0)

    • 数字非零:先结算待补的 “零”(见下),再输出 数字 + 该位权位单位
      ---- 注意非零位的单位是无条件输出的,不论它是 拾/佰/仟 还是 圆/萬/億.

    • 数字为零:

      • a) 若其右侧(即更低位,下标i+1 .. n-1)仍存在非零数字 → 置
        zeroPending,等遇到下一个非零数字时先补一个 “零”;
        连续多个零因此只会补出一个 “零”.
      • b) 若该位恰是级末单位位(w%4 == 1):
        • w == 1→ 永远输出 “圆”;
        • 否则 → 仅当"本级 4 位"(下标i-3 .. i,左端截断到 0)
          非全零时才输出级单位;本级全零则不输出该级单位.
        • 例:1000000000→ 拾億圆整.億位(i=1)本身是'0',但本级
          实际只有 2 位 “10”,非全零,故仍输出 “億”.
        • 例:100000001→ 壹億零壹圆整.萬级 4 位全零,故 “萬” 不出现.
    • 边界:整数位数n > maxUnitWidth()(即超过units表当前支持的最大权位,
      maxUnitWidth()动态计算,当前为 24 = 垓仟级)时由parseAmount直接返回
      错误码EC_TOO_LONG,不再静默降级,见 [四].

  • 小数部分(convertDecPart):处理 角(第 1 位)/ 分(第 2 位)/ 厘(第 3 位)/ 毫(第 4 位)/ 丝(第 5 位)/ 忽(第 6 位)六级.
    角或分为 0 时该位整位跳过(.20→ 贰角;.02→ 贰分).
    六位皆 0(无小数,或.00/.000000)→ 返回nil,由调用方补 “整”.
    第 6 位后由parseAmount直接丢弃,不做四舍五入(见 [四]).

  • 整数与小数之间的 “零”:
    整数部分有效(即intPart非空且非"0"),且小数以'0'开头(角=0)时,
    在圆后补一个 “零” 再接小数:

    1.01 → 壹圆零壹分; 10.05 → 拾圆零伍分

    若整数部分为空或"0",则既不输出 “圆”,也不补这个 “零”,直接接角分:

    .01 → 壹分; 0.5 → 伍角; .19 → 壹角玖分; -0.20 → 负贰角

    (注意:补零条件的判据是"整数部分有效",不是"输入以 0 开头".)


[三]字符表集中管理 + 下标序列统一输出(核心改造)

所有可能的"输出字符"都集中在三张map[int]string,键一律是"基址 + 偏移",
三个键空间互不重叠,故materialize可按键精确查表:

基址键空间
digitsDIGIT_BASE=00~9零~玖(偏移 d 即阿拉伯数字值本身)
unitsUNIT_BASE=100101~(随表扩展)圆/拾/佰/仟/萬/億/兆/京/垓/拾/佰/仟/拾/佰/仟/秭 …
auxAUX_BASE=200200~207负/整/角/分/厘/毫/丝/忽

units表的键空间上界由maxUnitWidth()动态决定,随表中权位条目的增减自动变化;
当前最大键为UNIT_BASE + 25(对应 “秭” 级).整数最大可表示位数即等于
maxUnitWidth()的返回值.

digits基址取 0,是因为它排在最前,不可能与他表冲突,偏移直接用阿拉伯数字值,
于是digits[d]里的d既是"数字值"也是"表键".既然如此,为什么还要显式定义
DIGIT_BASE?一是让三表结构对称,一眼看出键空间;二是把键空间判定收敛成具名
谓词(见下);三是万一将来调整基址,所有DIGIT_BASE + d的写法不会静默失效.

另有一张不参与字符输出的码表:

  • msgCodes:返回码表,键 1~5 为五类错误提示串(键 0 虽定义为""但从未被读取).

语义常量(避免魔法数字,均指向上述表的固定键):

IDX_LING = DIGIT_BASE+0 (零) IDX_YI = DIGIT_BASE+1 (壹) IDX_YUAN = UNIT_BASE+1 (圆) IDX_FU / IDX_ZHENG / IDX_JIAO / IDX_FEN / IDX_LI / IDX_HAO / IDX_SI / IDX_HU = AUX_BASE+0/1/2/3/4/5/6/7

“零” 在三处作"补零"下标(整数补零,圆后补零,零圆整),故与 “壹” 一样
提升为常量,不再裸写 0.

注:“拾” 没有独立常量----它在units中随w%4==2重复出现(拾/拾萬/拾億…),
因此由materializeunits[key] == "拾"动态判定,而不是固定下标.

键空间谓词(键空间判定的唯一出处,与三表基址绑定):

  • isDigitKey(idx)idx ∈ [DIGIT_BASE, DIGIT_BASE+10)→ 是数字位
  • isLevelUnit(key)key ≥ UNIT_BASE(key-UNIT_BASE)%4 == 1→ 是节单位
  • maxUnitWidth()遍历units表,返回当前支持的最大权位w(整数最大可表示位数)

转换逻辑(convertIntPart/convertDecPart)不直接拼接字符串,而是向
一个[]int下标序列 append:

  • 数字位 →digits的键(DIGIT_BASE + d;固定数字用IDX_LING/IDX_YI)
  • 权位单位 →units的键(UNIT_BASE + w)
  • 辅助符 → 语义常量IDX_FU/ZHENG/JIAO/FEN/LI/HAO/SI/HU指向的固定键

materialize(idxs []int) string是全文件唯一的"下标 → 字符"出口,内部三步:

  1. 定位最高位数字firstDigit(供情形B 判定);
  2. 扫一遍标记skipSet("壹拾"简写,见下);
  3. skipSet过滤后依次查digits → units → aux拼接.

三表都未命中的越界下标直接跳过(不 panic,也不输出占位符).整体O(n):
步骤② 每个位置只向前看 1~2 个元素.debugMat为真时,额外把idxs完整打到
stderr 供校验:逐元素打印下标值与字符,被跳过的元素追加<-- SKIPPED.

"壹拾"简写规则(业务取舍,并非通行财务规范;在 materialize 中实施)

当 数字"壹"(IDX_YI)紧邻后接 单位"拾" 时,满足下列任一情形则跳过"壹"
(即 “壹拾” → “拾”):

  • 情形A:“拾” 之后紧跟"节单位"(圆/萬/億/兆/京/垓,即w%4==1)或已到序列末尾.

    1000000000 → 拾億圆整; 100000 → 拾萬圆整; 10 → 拾圆整
  • 情形B:“壹” 是序列中最高位(第一个)数字,且该 “拾” 属个级.
    代码判据写作w ≤ 4;但既然该单位必须是 “拾”(w%4==2),候选w
    只有2/6/10/14/18,故实际恒等于w == 2,即个级的拾位.

    12.10 → 拾贰圆壹角; 15 → 拾伍圆整

反之保留 “壹”:若 “拾” 之后是普通数字位,且 “壹” 并非最高位数字----

160000 → 壹拾陸萬圆整(不可写成 陸萬)

若要恢复全式 “壹拾”,只需去掉materializeskipSet的置位逻辑,
其余代码(字符表,下标序列)无需改动.

这样做的好处:

  1. 字符定义与转换逻辑彻底解耦,改字形/改措辞只动表;
  2. 逻辑分支里只出现"下标",可读性高,易审计,无魔法数字;
  3. 统一出口,便于后续做去重,插值,调试打印下标等.

[四]边界与退化行为(以下均为实测确认的输出)

  • 零值→ “零圆整”(带负号时前缀 “负”),有两条互不相同的到达路径:

    • a)isZeroYuan字面量命中(在 DFA 之前,对清洗后整串做等值匹配):

      0 / 0. / 0.0 / 0.00 / . / .0 / .00
    • b) 整数部分为空或"0",且角分全为 0----走的是"整数与小数都产出空序列"
      的分支,例如0.000(它并不在清单 a 里).

  • 退化输入(DFA 接受,但一个数字也没有):

    "" → 零圆整 "-" / "-0" / "-." / "-0.00" → 负零圆整(负号照常前置)
  • 超长(直接报错):整数位数n > maxUnitWidth()(即超过units表当前支持的
    最大权位,当前为 24 = 垓仟级)时,parseAmount在扫描结束后立即返回错误码EC_TOO_LONG,
    msgCodes[5]给出中文提示,不再静默降级为零值:

    12345678901234567890123456 → [ERR]数值过大,整数部分超出可表示的最大位数(units最大权位以上)

    (小数位则无此限制:最多收 6 位 角/分/厘/毫/丝/忽,超出截断,不四舍五入.)

  • 小数截断:第 6 位起由parseAmount直接丢弃,不做四舍五入
    ----1234.567→ 壹仟贰佰叁拾肆圆伍角陸分柒厘.

  • 前置零:"00","0012"→ 错误码EC_LEAD_ZERO(只有0.xx/-0.xx允许).


[五]已知取舍 / 待办(动手改代码前请先读这一节)

  1. “壹拾” → “拾” 属本地业务取舍,不是通行财务大写规范(见 [三]).
  2. errState常量从未被赋值----ERROR态在代码里以"立即 return" 表达.
  3. msgCodes[0]从未被读取----成功路径直接返回转换结果,不查表,该条目保留仅为让码表自洽.
  4. 整数最大可表示位数由units表规模动态决定(见maxUnitWidth()),
    units表新增权位条目后无需改动任何数字判断逻辑,超长上限自动跟随扩展.
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/3 6:52:37

双曲线方程全解析:从定义推导到高考解题技巧

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/3 6:50:17

Linux之socket编程(四)----Tcp

Tcp tcp进行网络通信在很多部分和udp通信时很相似的 大纲 init 初始化和udp很相似 就是socket的第二个参数时sock_stream 除了上面这些 litsen Tcp依然还需要litsen接口 监听就是有点像接电话,有人打进来电话你就要接 start accept accept的返回值是有些不同的,相对于udp…

作者头像 李华
网站建设 2026/9/3 6:49:03

无线耳机固件升级全解析:从文件名解读到升级后优化

简介&#xff1a;本资源是JieLi&#xff08;杰理&#xff09;AC701n系列耳塞主控芯片JL701n的官方固件升级包v162&#xff0c;面向嵌入式音频设备开发者、固件工程师及硬件调试人员&#xff0c;用于完成耳塞产品的固件烧录、功能验证与ANC/LC3/Opus等音频特性适配。压缩包共124…

作者头像 李华
网站建设 2026/9/3 6:47:40

人才画像系统前端设计:业务驱动的信息决策架构

简介&#xff1a;本资源是一套基于Vue框架开发的人才画像系统前端页面设计源码&#xff0c;面向企业HR系统开发者、教育机构信息化建设人员及中高级前端工程师&#xff0c;聚焦人才技能、经验与行为特质的可视化呈现与交互管理&#xff0c;有效支撑招聘评估、梯队培养与岗位匹配…

作者头像 李华
网站建设 2026/9/3 6:47:31

AI Chat长回复流式渲染优化:从卡顿到4倍提速

最近在梳理 AI Chat 类产品的前端体验时&#xff0c;我重点盯了一个场景&#xff1a;Claude 网页版和桌面端在生成长回复时的流式渲染效果。从用户视角看&#xff0c;模型生成内容的等待时间已经明显变短&#xff0c;但页面上的文本却经常出现“打字机不连贯、代码块闪烁、长回…

作者头像 李华
网站建设 2026/9/3 6:46:54

三电平SVPWM仿真原理与工程实践:MATLAB/Simulink全链路实现

简介&#xff1a;本资源是一份面向电力电子与电机控制方向初学者及课程设计者的MATLAB仿真实践材料&#xff0c;聚焦三电平逆变器中空间矢量脉宽调制&#xff08;SVPWM&#xff09;策略的原理验证与波形分析。资源解决的核心问题是&#xff1a;如何在MATLAB中构建可运行、可调试…

作者头像 李华