好久不见,最近调岗了,调到了供应链,从明天开始要负责种植场的工作了。但在调来供应链的这段时间里,付款申请的业务实在是有点繁忙,所以就在筹划着是不是可以用技术来解决一下。在这个需求下,其中有一个所有的付款申请都会出现的功能 —— 小写货币数字转中文大写。
示例:
1234567.89 转换为壹佰贰拾叁万肆仟伍佰陆拾柒元捌角玖分整
今天抽了点时间出来写了一下,记录一下设计思路,防止忘记。
信息 | 说明 |
---|---|
操作系统 | Microsoft Windows 11 专业工作站版 |
开发工具 | Microsoft Visual Studio Community 2019 |
每次写代码想逻辑想不下去的时候,总会告诉自己:“用自己最朴素的想法去思考,如果是人工来判断,会怎么处理?” 我们都知道,中文大写数字的写法,实际上就是数字的读法。因此,从数字的读法出发,发现规律如下:
如上图,我们可以发现,读数字的时候每 4 个一组,每组有 1 个单位名称,即我们常说的:万、亿。一般公司里的付款申请多数都是万级的,鲜有少数的十万、百万,超过的都是极少的,但写代码需要尽可能考虑得全面一些,因此 Yogurt 特地去找了超过“亿”的单位名称。
早在一千五百多年前,我国出现了一本数学著作,名曰《孙子算经》,里面记载了万以上的单位名称,Yogurt 整理出来便是如上图所示。恰好也是每万倍一个名称,对上了上面自己找到的 “4 个一组” 的规律。不得不佩服我们的老祖宗在千年以前定好的逻辑一直可以沿用至今。
在读数的过程中,还发现了在一些特殊情况下,读法是有不同的。例如上述绿色区域的 0000
,按字面来说,应该读作 零零零零兆
,但实际上我们往往就直接读作 零
了;红色区域的 4050
,按字面来说,应该读作 四仟零佰五拾零
的,虽然有点反人类,但字面上应该是这么读的,而实际上我们会读作 肆仟零伍拾
;同样的情况也出现在蓝色区域中的 2003
中。简单整理一下这个规律就是:① 一组数字中出现 4 个 零
时,该组数字读 零
,无需读单位;② 一组数字中出现连续的 零
时,只读作一个 零
;③ 当小单位数字为 零
时,无需读 零
以及它的单位。
OK,整理一下上述的所有规律如下:
零
时不读数字及单位零
时,只读作一个 零
,且无需读出单位零
时,无需读 零
以及它的单位搞清楚原理和规律以后,就可以编写代码了。具体过程不再赘述,大家看代码吧
/*
* @Author: Yogurt_cry
* @Date: 2023-07-18 14:27:02
* @LastEditors: Yogurt_cry
* @LastEditTime: 2023-07-19 18:59:30
* @Description: 小写货币数字转换中文大写
* @FilePath: \WechatClientc:\Users\Yogurt_cry\Desktop\Untitled-1.svg
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace PersonalTool
{
public class NumConvert
{
///
/// 反转字符串
///
///
///
public static string StrRervse(string value)
{
char[] valueRervse = value.ToCharArray();
Array.Reverse(valueRervse);
return new string(valueRervse);
}
private static readonly string NUM_NAME = "零壹贰叁肆伍陆柒捌玖";
///
/// 数字转换中文货币大写
///
///
///
public static string ConvertCNYNum(string value)
{
// 出现异常直接返回空值
try
{
// 由于数字精度问题无法通过运算解决, 因此使用字符串
string[] valueSplit = value.Split('.');
string intPart = __CNNumIntPart__(StrRervse(valueSplit[0]));
// 整理清除多余的 "零"
while (intPart.Contains("零零")) intPart = intPart.Replace("零零", "零");
string decPart = __CNNumDecPart__(valueSplit.Length > 1 ? valueSplit[1] : "");
return string.Format("{0}元{1}整", intPart, decPart);
}
catch { return ""; }
}
///
/// 转换整数部分
///
///
///
private static string __CNNumIntPart__(string value)
{
string[] groupUnit = new string[] {
"", "万", "亿", "兆", "京", "垓", "秭", "穰", "沟", "涧", "正", "载", "极"
};
List<string> result = new List<string>();
int groupSize = value.Length / 4;
for (int i = 0; i <= groupSize; i++)
{
int size = i == groupSize ? value.Length % 4 : 4;
string cnNumGroup4 = __CNNumGroup4__(value.Substring(i * 4, size));
if (string.IsNullOrWhiteSpace(cnNumGroup4)) result.Add("零");
else result.Add(cnNumGroup4 + groupUnit[i]);
}
result.Reverse();
return string.Join("", result);
}
///
/// 分组处理大写转换
///
///
///
private static string __CNNumGroup4__(string value)
{
string[] wordNum = new string[] { "", "拾", "佰", "仟" };
string numWithout0 = int.Parse(value).ToString(); // 处理非必要零值
numWithout0 = numWithout0 == "0" ? "" : numWithout0;
int wdIndex = value.Length - numWithout0.ToString().Length;
string lstNum = "";
List<string> result = new List<string>();
foreach (char item in numWithout0)
{
string num = item.ToString();
string numWithUnit = "";
if (num == "0") { if (lstNum != "0") numWithUnit = "零"; }
else numWithUnit = NUM_NAME[int.Parse(num)].ToString() + wordNum[wdIndex];
result.Add(numWithUnit);
lstNum = num;
wdIndex += 1;
}
result.Reverse();
return string.Join("", result);
}
///
/// 转换小数部分
///
///
///
private static string __CNNumDecPart__(string value)
{
List<string> result = new List<string>();
if (value != "00")
{
string __value = value.Length < 2 ? value : value.Substring(0, 2);
string numUnit = "角分";
for (int i = 0; i < __value.Length; i++)
{
string num = __value[i].ToString();
num = NUM_NAME[int.Parse(num)].ToString();
if (num == "零") result.Add(num);
else result.Add(num + numUnit[i].ToString());
}
if (result.Last() == "零") result.RemoveAt(result.Count - 1);
}
return string.Join("", result);
}
}
}
此前一直负责的是开发工作,接需求做项目,现在直接调岗到业务部门,才发现很多需求并不是不存在,而是业务部门要么是压根并不认为是可以优化的,要么就是不知道居然还有优化的方法,要么就是就算觉得麻烦却不知道上哪去找人优化。这次深入业务部门,自己接手了一些常规工作,才发现好多可以优化的地方。慢慢来吧,一个个优化起来,这样推文也可以有素材更新了,哈哈哈哈哈哈。