首页 / 资讯中心 / 文章详情

C语言金额大写转换:查表分段与零合并规则实战

C语言金额大写转换:查表分段与零合并规则实战 ★ FEATURED ARTICLE
做过财务类系统对接的人大概都碰到过这个需求把一串阿拉伯数字金额转换成壹佰贰拾叁万肆仟伍佰陆拾柒元捌角玖分这样的大写形式。它的价值不在于显示好看而在于票据、合同、凭证这类场景里大写金额是唯一具备防篡改能力的那一层。数字1234567.89改一个字符就能变出十倍百倍的差异壹佰贰拾叁万改一笔却很难不留痕迹所以金额大小写转换本质上是一个安全需求而不是一个格式化需求。这篇文章围绕查表分段和零合并规则两个核心机制展开讲清楚怎么用C语言把这套逻辑写稳。适合已经能写基本C代码、但在这个需求上被边界条件折磨过的读者也适合刚接触业务型字符串处理的同学拿来当参考实现。文中所有代码都是可以直接编译运行的完整片段不讲空泛理论。1. 先把问题看清楚金额大写转换到底难在哪里1.1 表面上是个映射题实际上是语法题很多人第一次接到这个需求脑子里冒出来的方案是查个表替换一下。零到玖对应零壹贰叁肆伍陆柒捌玖小数点换成元角分看上去三个数组就能搞定。真正动笔之后十分钟就会发现问题一百零一应该读壹佰零壹一千零一十应该读壹仟零壹拾一万零一应该读壹万零壹而一十万零一百零一这种更长的数字中间到底出现几个零靠简单的逐位替换根本推不出来。原因在于中文数字的书写规则不是逐位拼接而是带语法结构的。它有一套权位体系个、拾、佰、仟、万、亿有一套省略规则连续的零只读一个还有一套前缀规则小节开头如果高位全是零要不要补零。这三层规则叠在一起本质上是一个小型的形式语言问题而不是字符串替换问题。所以正确的做法是先把规则抽干净再考虑用什么数据结构承载它。我在实际项目里踩过的第一个坑就是一开始按逐位查表遇到零跳过写代码不到三十行看着很清爽结果测试用例一跑一百一十被转成了壹佰壹拾这个对的一千零一十被转成了壹仟零壹拾也对但是一万零一十被转成了壹万零壹拾——少了一个零而且更麻烦的是壹拾万和拾万两种写法在不同系统里有不同约定你得先确定口径。1.2 需求边界先定口径再写代码在动手之前有几条口径必须先和需求方对齐否则写完必返工。这些细节在需求文档里通常不会写但每一个都能引发一次线上问题。口径项常见取值影响是否使用圆多数场景用元少数票据用圆影响后缀字符零字是否用〇金融场景基本用零影响字符表整数部分为0时怎么写零元整 / 零元零角零分影响空值处理角为零、分不为零壹佰元零伍分 / 壹佰元伍分影响补零逻辑负数金额前缀负或直接拒绝影响入参校验万位与亿位是否允许超限一般限制到万亿级影响缓冲区大小提示口径不确认就开工最典型的返工点是零元整和零元零角零分这两个写法。前者在大部分银行票据模板里通用后者多见于较早的打印系统。这类差异不是技术问题是业务约定问题一定要白纸黑字确认。1.3 输入形式的选择别用 double 存金额这一条值得单独拎出来说。我见过不少实现直接拿 double 接金额然后在最后乘 100 取整。这在多数情况下能跑通但在临界值上会出现经典问题比如 0.29 乘 100 得到 28.999999999999996截断后变成 28 分一毛钱就这么丢了。正确的做法是在函数入口就把金额整数化。要么上游直接传分为单位的 long long要么传字符串再自己解析成分。用 long long 存分最大值约 9.2e18 分也就是九千多万亿元远远超出正常业务范围不会溢出。这个选择还顺带解决了另一个问题整数运算没有精度损失角分部分的拆分就是简单的除法和取模逻辑干净得多。/* 推荐入口以分为单位的整数 */ void amount_to_chinese(long long cents, char *out, size_t out_size); /* 如果上游只能给字符串就在这一层做解析不要让它流进核心逻辑 */ int parse_amount_str(const char *s, long long *cents);把精度问题挡在核心算法之外是我在这个需求上收获最大的一条经验。核心逻辑只处理整数不用考虑浮点后面的所有推导都能用整数运算保证正确性。2. 查表的三层结构数字、节内单位、节间单位2.1 为什么坚持查表而不是 switch这个问题被问过几次我的答案很直接因为查表的映射关系是数据switch 的映射关系是代码而数据比代码好改。金额大写转换的字符映射在不同系统里是有差异的贰在某些老模板里写成貳陆有人写陸如果映射埋在 switch 里改一次要翻遍所有分支写成常量数组改一行就完成。第二个理由是查表天然支持下标即语义。中文大写数字零到玖正好十个顺序和阿拉伯数字一一对应那么 digit_cn[d] 就是第 d 个大写字符不需要任何条件判断。这种下标对齐的写法在可读性上远胜一串 if-else也让后面的调试变得轻松——出了问题直接打印数组内容就能确认映射对不对。第三个理由偏工程查表把字符和逻辑解耦了。你完全可以把这三张表做成配置文件、做成多语言资源、做成运行时可替换的注入项而算法主体一行都不用动。对于要做多套票据模板的系统这个解耦省下的工作量相当可观。2.2 数字位表与十六个字符的字面量最基础的一张表就是数字映射。这里我用静态常量数组长度固定为 10不写出大小让编译器推导避免手滑写错长度。static const char *const DIGIT_CN[10] { 零, 壹, 贰, 叁, 肆, 伍, 陆, 柒, 捌, 玖 };注意每个字符串都是三个字节UTF-8 下每个汉字占三字节这一点在后面拼接时非常关键。如果代码里对一个字的理解是一个字节那所有长度计算都会错。我个人习惯在文件顶部加一个宏明确表达一个汉字占几个字节这个事实虽然它不能真的适配所有编码但至少让读代码的人知道这里用的是 UTF-8。#define CN_CHAR_BYTES 3 /* 本文件的字符表统一按 UTF-8 处理 */接着是节内单位表四位一节以内从低到高是个、拾、佰、仟。个位不需要单位字符所以第一项是空字符串。这张表只有四项但它的下标含义必须和位权严格对应写反了整段就全错。static const char *const UNIT_IN_SECTION[4] { , 拾, 佰, 仟 };2.3 节间单位表与超大数的扩展余量第三张表是节间单位也就是每四位一节的整体权重第 0 节最低四位没有节间单位第 1 节是万第 2 节是亿第 3 节是万亿。static const char *const SECTION_UNIT[4] { , 万, 亿, 万亿 };这里有个细节值得说明中文的节间单位并不是严格四位一进亿之后是十亿、百亿、千亿然后才是万亿。所以四节结构已经能覆盖到 16 位十进制数9999 万亿也就是 9999 万亿元正常业务绝对够用。真要再往上需要引入兆这一级但那属于超出行情的需求我一般直接在上层做长度校验超过 15 位就返回错误码而不是硬撑着支持。注意有些人为了通用把 SECTION_UNIT 写成十项以为能支持到极大数但中文里兆之后的单位读法在实务中没有统一标准各家写法不一致。与其支持一个不确定的范围不如明确限制并给出清晰的错误提示这比转换出一个没人认识的结果要好。三张表加起来不到二十行但它们是整个算法的地基。我在后面所有关于零合并的讨论都建立在这三张表的基础上——因为零的处理本质上是要不要往输出里插入 DIGIT_CN[0]的问题映射本身不参与判断。3. 四位一节的分段把长数字切成可以独立处理的小块3.1 分段不是拍脑袋是跟着中文的读法走中文数字的读法天然按四位分组。一亿两千三百四十五万六千七百八十九里面一亿和两千三百四十五万和六千七百八十九是三个独立的语音块每块内部的读法完全一致千、百、十、个块与块之间只差一个节间单位。这个观察直接决定了算法结构把整数部分按四位切开每块单独转成四个字以内的字符串再拼上对应的节间单位块之间的接缝处单独处理零的问题。这个思路的好处是把一个 N 位的问题降解成若干个 4 位的问题每块的处理逻辑完全复用代码量和出错面积都大幅压缩。更要紧的是零合并规则里最麻烦的跨节连续零就被隔离到了块与块的接缝这一层块内部只需要处理块内的零两件事互不干扰测试也容易分开做。分段的实现就是把整数不断对 10000 取模和整除从低位往高位收集int sections[4]; int n_sec 0; long long tmp yuan; while (tmp 0 n_sec 4) { sections[n_sec] (int)(tmp % 10000); tmp / 10000; }收集完之后 sections[0] 是最低的四位sections[n_sec-1] 是最高节。转换时要从高位往低位遍历因为人是从左往右读的。这个收集顺序和输出顺序相反的小细节是新手最容易绕晕的地方我自己第一次写的时候就在循环方向上纠缠了半天。3.2 单个四位数块怎么转一个标志位搞定块内零块内转换的核心是一个叫做待补零的布尔量。规则很简单从左往右扫四位遇到非零数字就输出数字位权遇到零就先挂起一个标记等到下一个非零数字出现时如果之前挂起过标记就补一个零再输出当前数字。用这个思路处理 1010壹仟零壹拾千位 1输出壹仟标记清零百位 0挂起标记十位 1标记还在先补零再输出壹拾标记清零个位 0挂起标记但循环结束标记不再使用。结果壹仟零壹拾正确。处理 1000壹仟千位 1 输出壹仟百位、十位、个位都是 0标记挂起但后面没有非零数字来触发所以不补零。结果壹仟正确。这就是块内尾部零不补零的规则它必须靠只在遇到非零数字时才补零这个时机来保证而不是靠事后裁剪字符串。static int convert_section(int num, char *out) { int len 0, started 0, zero_pending 0; int div 1000; for (int i 3; i 0; --i) { int d (num / div) % 10; div / 10; if (d 0) { if (started) zero_pending 1; continue; } if (zero_pending) { len sprintf(out len, %s, DIGIT_CN[0]); zero_pending 0; } len sprintf(out len, %s%s, DIGIT_CN[d], UNIT_IN_SECTION[i]); started 1; } return len; }代码里有个 started 标志作用是挡住块内前导零。比如块值是 23千位和百位都是零如果不管不顾地挂起标记输出就会变成零贰拾叁多了一个零。加上 started 之后在还没有输出过任何数字之前零直接跳过不挂标记。这个判断很容易漏漏了之后表现是小的节前面总多一个零而大的节又正常非常迷惑人。3.3 节间单位插入与整节为零的跳节拼完每一节的块内字符紧接着追加这一节对应的节间单位。比如第 1 节处理完是壹仟贰佰拼上万就成了壹仟贰佰万。这里有个必须处理的边界如果这一节的数值是 0那么整节跳过既不出字符也不出单位。跳节看起来简单但它和零合并有耦合。考虑一百万零三元这个数1,000,003。三节分别是 [3, 0, 1]最高节是 1亿位不对n_sec3第 2 节是亿吗。这里要注意100 万是 1,000,000按四位切是 0,0100,0000 也就是三节 [0, 100, 0]不对。重新算1000003 对 10000 取模是 3整除得 100100 对 10000 取模是 100整除得 0。所以 sections[0]3, sections[1]100, n_sec2。第 1 节是万值是 100块内转换出来是壹佰千位零、百位壹、十位零、个位零拼上万得到壹佰万然后第 0 节是 3输出叁。接缝处应该是壹佰万零叁那个零由谁出由后面要讲的跨节补零逻辑出。如果只做简单的本节非零就直接拼结果会变成壹佰万叁漏了零。这说明节间拼接绝不能只做字符串连接接缝处必须过一遍规则判定。4. 零合并规则全篇最容易翻车的地方4.1 把零的问题拆成四种独立情形零的规则听上去只有一句连续零只读一个但实际实现的时候它至少分成四种情形每种触发条件都不同。我建议在写代码之前先在纸上把这四种情形列出来分别想清楚什么时候补零、补在哪里然后一个情形一个分支地实现。混在一起写是灾难。情形举例处理块内连续零1001 → 壹仟零壹块内 zero_pending 处理块内尾部零1200 → 壹仟贰佰不补零靠只遇非零才补保证跨节整节为零100000003 → 壹亿零叁跳节时挂起跨节待补零标记跨节小节高位为零10001 → 壹万零壹接缝处高位为零需补零这四类里前两类在块内函数就解决了后两类必须在外层的节间循环里解决。分层清楚之后每一层的代码都很短测试点也一一对应出问题能立刻定位到是哪一层。4.2 跨节待补零一个一直在传的标志位外层循环里我需要维护一个跨节的待补零标志。它的生命周期是这样的遇到整节为零时置位遇到非零节时如果标志是置位的就先往输出里补一个零再转换本节然后把标志清掉。int need_zero 0; /* 跨节的待补零标记 */ for (int i n_sec - 1; i 0; --i) { int sec sections[i]; if (sec 0) { if (len 0) need_zero 1; continue; } if (len 0 (need_zero || sec 1000)) { len sprintf(out len, %s, DIGIT_CN[0]); } need_zero 0; len convert_section(sec, out len); len sprintf(out len, %s, SECTION_UNIT[i]); }注意判定条件写的是len 0 (need_zero || sec 1000)这里把两种补零情形合并了need_zero 对应前面有整节为零sec 1000 对应本节的千位为零。合并之后只补一个零不会出现零零这种重复。为什么 sec 1000 要补零因为这一节的千位是零意味着它在读的时候会有一个短暂的停顿中文里这个停顿就体现为零。比如 10001 是 1,0001两节 [1, 1]第 1 节是 1输出壹万第 0 节是 1小于 1000补一个零再输出壹得到壹万零壹。这也正是标准的读法。反过来说如果第 0 节是 1001也就是 11001两节 [1001, 1]第 0 节的千位是 1 不是零不需要补零直接输出壹仟零壹整体壹万壹仟零壹。这个读法也是对的因为壹万壹仟中间没有停顿。4.3 一个真实踩过的 bug重复补零我要专门讲这个 bug因为它很典型。早期版本的代码里我在跳节时给 need_zero 置位在转换非零节之前无条件检查 need_zero 补零然后在转换节内容之前又加了一段如果本节小于 1000 且元串非空则补零。两段逻辑分开写的各自看都没问题合起来就出事。具体表现在 100000003 这个数上三节 [3, 0, 1]第 2 节是 1输出壹亿第 1 节是 0need_zero 置位第 0 节是 3第一段逻辑看到 need_zero 补了个零第二段逻辑看到 3 小于 1000 又补了个零结果变成壹亿零零叁。排查过程没什么巧办法就是打印中间结果。我在每一节转换前后把 len 和输出缓冲打出来一眼就看出零是在哪一段被加的。修复办法就是上面代码里那个合并条件两段判断并成一个用一个表达式控制。这也印证了前面那句话零的四种情形必须统一在一个地方判定分散判定必然重复触发。提示调试这类字符串拼接问题时不要只看最终结果在每次 sprintf 之后打一行日志标出当前处理的节号和累计长度。字符长度在 UTF-8 下是 3 的倍数一旦出现长度对不上 3 的倍数说明混进了 ASCII 字符这往往能立刻定位问题。5. 角和分小数部分的独立小逻辑5.1 角分处理不适合并进主循环有人试图把角分也塞进四节的结构里理由是角是十分位分是百分位。这个思路技术上可行但我不建议因为角分有自己的一套独立规则角为零时要不要读、分不为零时前面补不补零、两者都为零时怎么收尾。这些规则和整数部分的节间规则完全不同硬塞进去会让主循环变得难读。更自然的做法是把整数和角分当作两个独立部件各自转完再拼。角分只有两位处理起来只有四个分支用一个小的 switch 或者几个 if 就能写完逻辑一目了然。int jiao (int)((cents % 100) / 10); int fen (int)(cents % 10);5.2 四种组合的判定与拼接顺序int、角、分这三部分的状态组合只有有限种把它们列全就不会漏角非零、分非零输出X角Y分如 1.23 → 壹元贰角叁分。角非零、分为零输出X角后面加整还是不加多数口径不加整写壹元贰角。也有要求写壹元贰角整的按需求定。角为零、分非零整数部分非零时补一个零再写分如 1.05 → 壹元零伍分整数部分为零时通常写零元零伍分或直接伍分按需求定。角分为零整数部分非零写X元整整数部分为零写零元整。我在实现里把第 3 种情形的补零做成了硬规则理由是壹元伍分读起来有歧义容易被理解成一元五角。补一个零字壹元零伍分读起来就毫无歧义。这个细节在实际票据打印里被投诉过一次改完之后就再没人提过。int len 0; if (yuan 0) { len convert_integer(yuan, out len); len sprintf(out len, 元); } if (jiao 0 fen 0) { if (yuan 0) len sprintf(out len, 零元); len sprintf(out len, 整); } else { if (jiao 0) { len sprintf(out len, %s角, DIGIT_CN[jiao]); } else if (fen 0 yuan 0) { len sprintf(out len, %s, DIGIT_CN[0]); } if (fen 0) { len sprintf(out len, %s分, DIGIT_CN[fen]); } }这段逻辑里yuan 0的情况被单独处理输出零元。如果 jiao 和 fen 也都是零最终得到零元整。这个结果符合大多数票据模板的要求。5.3 零元与负数的收尾约定零元整这个特例值得多说两句。函数被调用时如果传进来的是 0整数部分是 0直接走零元整分支。这里最容易犯的错是让 convert_integer 返回空串然后整段输出只剩元整看起来像被截断了。所以 convert_integer 在入参为 0 时必须明确输出一个零而不是什么都不输出。负数在实务里一般不允许出现在票据上但接口层面不能假设调用方一定守规矩。我的处理方式是在入口校验 cents 0 直接返回错误码不输出任何内容。这样调用方能通过返回值立刻发现问题而不是拿到一个带负号的奇怪字符串。如果业务确实需要表示负数比如记账方向那也应该在上层用借贷之类的字段表达而不是塞进金额字符串里。注意角分部分不要经过任何浮点运算。cents % 100 得到的余数就是角分编码除以 10 得角模 10 得分。这两步全是整数运算没有任何精度风险。一旦中间引入 double就前功尽弃了。6. 完整实现与验证把前面几块拼起来6.1 函数职责划分与缓冲区约定把前面的片段组织成一个完整的实现结构大概是三到四个函数入口函数负责参数校验和总调度整数转换函数负责分节和节间拼接单节转换函数负责块内四位辅助函数负责输出长度的安全检查。#include stdio.h #include string.h #include stddef.h static const char *const DIGIT_CN[10] { 零, 壹, 贰, 叁, 肆, 伍, 陆, 柒, 捌, 玖 }; static const char *const UNIT_IN_SECTION[4] { , 拾, 佰, 仟 }; static const char *const SECTION_UNIT[4] { , 万, 亿, 万亿 }; static int convert_section(int num, char *out) { int len 0, started 0, zero_pending 0, div 1000; for (int i 3; i 0; --i) { int d (num / div) % 10; div / 10; if (d 0) { if (started) zero_pending 1; continue; } if (zero_pending) { len sprintf(out len, %s, DIGIT_CN[0]); zero_pending 0; } len sprintf(out len, %s%s, DIGIT_CN[d], UNIT_IN_SECTION[i]); started 1; } return len; } static int convert_integer(long long yuan, char *out) { if (yuan 0) return sprintf(out, %s, DIGIT_CN[0]); int sec[4], n 0; long long t yuan; while (t 0 n 4) { sec[n] (int)(t % 10000); t / 10000; } int len 0, need_zero 0; for (int i n - 1; i 0; --i) { if (sec[i] 0) { if (len 0) need_zero 1; continue; } if (len 0 (need_zero || sec[i] 1000)) { len sprintf(out len, %s, DIGIT_CN[0]); } need_zero 0; len convert_section(sec[i], out len); len sprintf(out len, %s, SECTION_UNIT[i]); } return len; } int amount_to_chinese(long long cents, char *out, size_t out_size) { if (!out || out_size 128) return -1; if (cents 0) return -2; if (cents 10000000000000000LL) return -3; /* 超出万亿级上限 */ long long yuan cents / 100; int jiao (int)((cents % 100) / 10); int fen (int)(cents % 10); int len 0; if (yuan 0) { len convert_integer(yuan, out len); len sprintf(out len, 元); } if (jiao 0 fen 0) { if (yuan 0) len sprintf(out len, 零元); len sprintf(out len, 整); } else { if (jiao 0) { len sprintf(out len, %s角, DIGIT_CN[jiao]); } else if (fen 0 yuan 0) { len sprintf(out len, %s, DIGIT_CN[0]); } if (fen 0) len sprintf(out len, %s分, DIGIT_CN[fen]); } out[len] \0; return len; }缓冲区大小这一块我给的最小值是 128 字节。十六位数字每四位最多四个汉字加一个单位四节合计不到 60 个汉字加上元角分整最多 4 个总共不到 200 字节128 是保守估计但足够。用 out_size 做入口校验是一种防御性写法能挡住调用方传小缓冲导致的越界写。返回负数错误码比返回 void 更好用调用方可以据此做区分处理。6.2 一组必须跑过的测试用例实现写完之后真正花时间的是测试。我整理了一组覆盖度较高的用例基本上把前面讨论的所有规则都覆盖到了建议直接拿去当回归集。输入分期望输出覆盖点0零元整全零特例5零元零伍分整数为零、分为零50零元伍角整数为零、角非零105壹元零伍分角为零补零123壹元贰角叁分常规角分100壹元整角分为零1010壹拾元壹角十位起始100010壹仟元壹角块内尾部零1000100壹万零壹元整跨节补零1000000001壹仟万零壹元壹分跳节补零123456789壹佰贰拾叁万肆仟伍佰陆拾柒元捌角玖分全量程10000000壹拾万元整尾节为零100000000000壹拾亿元整亿级单位跑这组用例的时候第 9 行和第 10 行是最容易挂的。第 9 行 1000100 分等于 10001 元两节 [1, 1]第 0 节小于 1000 要补零得到壹万零壹元整。第 10 行 1000000001 分等于 10000000.01 元整数部分 10000000两节 [0, 1000]第 0 节是 0 被跳过并挂起 need_zero第 1 节是 1000 输出壹仟万然后第 0 节的整节为零在循环里被 continue 掉了need_zero 置位但后面没有非零节来消费它。这是个漏洞。整节为零的时候那个零在跳节之后没有机会被补出来。解决方式是在循环结束后检查 need_zero如果还有挂起的零说明最低节为零但更高节非零此时应该什么都不补——因为壹仟万后面接元时中文并不读壹仟万零元。所以这个用例的期望输出应该是壹仟万元零壹分也就是整数部分壹仟万角为零、分非零补一个零再写壹分。这里我要修正上面的表格1000000001 分对应的整数元是 10000000转换结果应为壹仟万元零壹分。这个零来自角为零分的补零逻辑不是来自节间补零。这说明一个事实一层逻辑的疏漏往往会被另一层逻辑碰巧掩盖测试用例必须足够细才能把这些掩盖点暴露出来。6.3 性能与可重入性的补充说明这个函数在票据打印场景里调用频率很低性能完全不是瓶颈一组转换的耗时在微秒级没必要做任何优化。真正需要注意的是可重入性和线程安全。上面的实现里所有表都是 const 常量没有静态可变状态所有输出都写到调用方提供的缓冲区天然就是可重入的。这一点在多线程环境下很重要我见过有实现把结果写进函数内部的 static 数组再返回指针那种写法在并发下会直接串数据。另一个容易被忽略的点是 locale。如果程序的其他部分调用过 setlocalesprintf 对 %s 的行为通常不受影响但如果用到了 %ls 或者宽字符函数就可能出问题。我的建议是整个模块统一用窄字符加 UTF-8 字面量不碰宽字符避免编码层面的意外。字符表里全是三字节汉字拼接时用 %s 逐段追加最后补 \0这套做法在主流编译器上都稳定。如果后续要支持繁体写法或者圆字改动点非常集中只需要替换 DIGIT_CN 和 SECTION_UNIT 的字符串字面量算法主体一个字都不用动。这就是前面坚持查表的回报——规则和数据分离之后变化的成本被压到了最低。6.4 我在实际使用中总结的几条习惯第一条先写测试表再写代码。这不是形式主义是因为这个需求的边界条件太多了靠脑子想一定会漏。把表格列出来每一行对应一个分支写代码的时候照着填写完立刻能验证效率远比写完再想用例高。第二条所有中间结果都打日志。字符串拼接类的 bug 特别难靠肉眼看出来因为最终结果往往只错一个字而那个字可能是三个地方任何一个产生的。在每次 sprintf 之后打印 len 和缓冲内容能让你在几十秒内定位问题。第三条不要试图用一个通用函数解决所有模板差异。我最早写过一个带八个开关参数的万能转换函数支持圆/元、零/〇、整/正、有无角分等组合结果参数多到自己都不敢调用。后来拆成了核心转换 后处理替换两层核心只出一种标准形式特殊写法由上层做字符串替换代码立刻清爽了。第四条超过万亿的需求一定要在文档里明确拒绝。中文大数单位在万亿之后没有公认写法硬撑着支持只会制造麻烦。在接口约定里写清楚上限比转换出一个没人认识的结果要负责得多。
阅读完成 · 觉得有帮助?
咨询建站