当前位置:网站首页>数字与中文大写数字互转(5千万亿亿亿亿以上的数字也支持转换)

数字与中文大写数字互转(5千万亿亿亿亿以上的数字也支持转换)

2022-08-09 22:04:00 小小明-代码实体

来源是看到知乎上一个奇葩问题:

image-20220807203558599

任意大小的数字字符串转中文

其实10万以内的整数转换大写也太简单,今天我们要实现的是任意大小的数字字符串转大写。

首先我们要保证4位数以内的转换函数,能够处理各种含"0"的情况,代码:

import re

ch_num = '零一二三四五六七八九'
units = ' 十百千'


def num2Chinese1(num_str):
    "转换9999以内的数字到大写"
    result = [ch_num[int(i)]+unit for i,
              unit in zip(reversed(num_str), units)]
    result = "".join(reversed(result))
    result = re.sub("(?:零[十百千])+", "零", result)
    result = re.sub("零+", "零", result)
    result = result.rstrip()
    if result != "零":
        result = result.rstrip("零")
    return result

然后生成测试用例并测试:

import random
import itertools

num_t = "123456789"
cases = []
for length in range(1, 5):
    nums = random.sample(num_t, length)
    cases.append("".join(nums))
    for n in range(1, length+1):
        # n个0
        for poss in itertools.combinations(range(length), n):
            num = nums.copy()
            for pos in poss:
                num[length-pos-1] = "0"
            num = "".join(num)
            cases.append(num)
for i in cases:
    print(i, num2Chinese1(str(i)))
6 六
0 零
54 五十四
50 五十
04 零四
00 零
831 八百三十一
830 八百三十
801 八百零一
031 零三十一
800 八百
030 零三十
001 零一
000 零
6895 六千八百九十五
6890 六千八百九十
6805 六千八百零五
6095 六千零九十五
0895 零八百九十五
6800 六千八百
6090 六千零九十
0890 零八百九十
6005 六千零五
0805 零八百零五
0095 零九十五
6000 六千
0800 零八百
0090 零九十
0005 零五
0000 零

保证了四位数的处理函数没有问题后,然后就可以开始编写能够支持任意位数的处理函数,并将转换函数升级到支持小数。

import math
import re

ch_num = '零一二三四五六七八九'
units = ' 十百千'

def num2Chinese1(num_str):
    "转换9999以内的数字到大写"
    result = [ch_num[int(i)]+unit for i,
              unit in zip(reversed(num_str), units)]
    result = "".join(reversed(result))
    result = re.sub("(?:零[十百千])+", "零", result)
    result = re.sub("零+", "零", result)
    result = result.rstrip()
    if result != "零":
        result = result.rstrip("零")
    return result


def num2Chinese2(num_str):
    if len(num_str) <= 4:
        result = num2Chinese1(num_str[-4:])
    else:
        result = num2Chinese1(num_str[:-4]) + "万" + num2Chinese1(num_str[-4:])
    return result.replace("零万", "零")


def num2Chinese3(num_str):
    num_str = num_str.lstrip("0")
    if not num_str:
        return "零"
    if len(num_str) <= 4:
        return num2Chinese1(num_str)
    e_num = math.ceil(len(num_str)/8)
    result = [num2Chinese2(num_str[-8:])]
    for e_i in range(1, e_num):
        result.append(num2Chinese2(num_str[-8-e_i*8:-e_i*8])+"亿"*e_i+" ")
    result = "".join(reversed(result))
    result = re.sub(" (?:零+亿+ )+", " 零", result)
    result = re.sub("零(亿+)", r"\1", result)
    result = re.sub("零+", "零", result)
    return result.strip("零")


def num2Chinese(num_str):
    num_str = num_str.replace(" ", "").rstrip(".")
    if not re.fullmatch("\d+(?:\.\d+)?", num_str):
        raise Exception("不是一个数字字符串")
    if num_str.find(".") != -1:
        num_str1, num_str2 = num_str.split(".", 1)
        return num2Chinese3(num_str1)+" 点 "+"".join(ch_num[int(c)] for c in num_str2.rstrip("0"))
    return num2Chinese3(num_str)

下面我们生成千万亿级别的各种具备代表性的测试数据进行测试:

nums_t = random.choices("123456789", k=16)
num = "".join(nums_t)
print(num, num2Chinese(num))
for zero_len in range(4, 15, 3):
    for s_pos in range(17-zero_len):
        nums = nums_t.copy()
        nums[s_pos:s_pos+zero_len] = "0"*zero_len
        num = "".join(nums)
        print(num, num2Chinese3(num))
9681852243533934 九千六百八十一万八千五百二十二亿 四千三百五十三万三千九百三十四
0000852243533934 八千五百二十二亿 四千三百五十三万三千九百三十四
9000052243533934 九千万零五百二十二亿 四千三百五十三万三千九百三十四
9600002243533934 九千六百万零二十二亿 四千三百五十三万三千九百三十四
9680000243533934 九千六百八十万零二亿 四千三百五十三万三千九百三十四
9681000043533934 九千六百八十一万亿 四千三百五十三万三千九百三十四
9681800003533934 九千六百八十一万八千亿 零三百五十三万三千九百三十四
9681850000533934 九千六百八十一万八千五百亿 零五十三万三千九百三十四
9681852000033934 九千六百八十一万八千五百二十亿 零三万三千九百三十四
9681852200003934 九千六百八十一万八千五百二十二亿 零三千九百三十四
9681852240000934 九千六百八十一万八千五百二十二亿 四千万零九百三十四
9681852243000034 九千六百八十一万八千五百二十二亿 四千三百万零三十四
9681852243500004 九千六百八十一万八千五百二十二亿 四千三百五十万零四
9681852243530000 九千六百八十一万八千五百二十二亿 四千三百五十三万
0000000243533934 二亿 四千三百五十三万三千九百三十四
9000000043533934 九千万亿 四千三百五十三万三千九百三十四
9600000003533934 九千六百万亿 零三百五十三万三千九百三十四
9680000000533934 九千六百八十万亿 零五十三万三千九百三十四
9681000000033934 九千六百八十一万亿 零三万三千九百三十四
9681800000003934 九千六百八十一万八千亿 零三千九百三十四
9681850000000934 九千六百八十一万八千五百亿 零九百三十四
9681852000000034 九千六百八十一万八千五百二十亿 零三十四
9681852200000004 九千六百八十一万八千五百二十二亿 零四
9681852240000000 九千六百八十一万八千五百二十二亿 四千万
0000000000533934 五十三万三千九百三十四
9000000000033934 九千万亿 零三万三千九百三十四
9600000000003934 九千六百万亿 零三千九百三十四
9680000000000934 九千六百八十万亿 零九百三十四
9681000000000034 九千六百八十一万亿 零三十四
9681800000000004 九千六百八十一万八千亿 零四
9681850000000000 九千六百八十一万八千五百亿 
0000000000000934 九百三十四
9000000000000034 九千万亿 零三十四
9600000000000004 九千六百万亿 零四
9680000000000000 九千六百八十万亿 
0000000000000000 零

目测已经覆盖所有基本情况,下面我们测试一个带小数的超大数字:

num2Chinese("9506335 25566321 00000000 18945760 00424584 54444444 00000000 00000000 00000000 44487878 00000000 44487878.00980213")
'九百五十万六千三百三十五亿亿亿亿亿亿亿亿亿亿亿 二千五百五十六万六千三百二十一亿亿亿亿亿亿亿亿亿亿 零一千八百九十四万五千七百六十亿亿亿亿亿亿亿亿 零四十二万四千五百八十四亿亿亿亿亿亿亿 五千四百四十四万四千四百四十四亿亿亿亿亿亿 零四千四百四十八万七千八百七十八亿亿 零四千四百四十八万七千八百七十八 点 零零九八零二一三'

测试一个python所支持的最大整数:

num2Chinese("9223372036854775807")
'九百二十二亿亿 三千三百七十二万零三百六十八亿 五千四百七十七万五千八百零七'

可以初步判断能够正确转换。

中文数字转数字

上面我们连如此复杂的数字转中文都实现了,中文转数字将会变得非常简单,完整代码如下:

import re

num_ch = dict(zip('零一二三四五六七八九', map(str, range(10))))
num_units = dict(zip('十百千', range(1, 4)))
num_units[""] = 0


def Chinese2Num1(ch_num_str):
    result = ["0"]*4
    for num, unit in re.findall("([^零十百千])([十百千]|$)", ch_num_str):
        result[num_units[unit]] = num_ch[num]
    result.reverse()
    return "".join(result)


def Chinese2Num2(ch_num_str):
    if ch_num_str.find("万") != -1:
        ch_num_str1, ch_num_str2 = map(Chinese2Num1, ch_num_str.split("万", 1))
        return ch_num_str1 + ch_num_str2
    return Chinese2Num1(ch_num_str).zfill(8)


def Chinese2Num3(ch_num_str):
    data = re.findall("[ 零]*([^亿]+)(亿+|$)", ch_num_str)
    result = ["0"*8]*(len(data[0][1])+1)
    for Chinese_str, e_str in data:
        result[len(e_str)] = Chinese2Num2(Chinese_str)
    result.reverse()
    return "".join(result).lstrip("0")


def Chinese2Num(ch_num_str):
    ch_num_str = ch_num_str.replace(" ", "").rstrip("点零")
    if ch_num_str == "":
        return "0"
    if not re.fullmatch("[零一二三四五六七八九十百千万亿]+(?:点[零一二三四五六七八九十百千万亿]+)?", ch_num_str):
        raise Exception(f"{
      ch_num_str}存在非法字符,无法转换")
    if ch_num_str.find("点") != -1:
        ch_num_str1, ch_num_str2 = ch_num_str.split("点", 1)
        return Chinese2Num3(ch_num_str1)+"."+"".join(num_ch[c] for c in ch_num_str2).rstrip("0")
    return Chinese2Num3(ch_num_str)

测试一下:

Chinese2Num('九百五十万六千三百三十五亿亿亿亿亿亿亿亿亿亿亿 二千五百五十六万六千三百二十一亿亿亿亿亿亿亿亿亿亿 零一千八百九十四万五千七百六十亿亿亿亿亿亿亿亿 零四十二万四千五百八十四亿亿亿亿亿亿亿 五千四百四十四万四千四百四十四亿亿亿亿亿亿 零四千四百四十八万七千八百七十八亿亿 零二百六十五万三千零二十一 点 零零九八零二一三')
'95063352556632100000000189457600042458454444444000000000000000000000000444878780000000002653021.00980213'

测试

接下面我们可以让两个转换函数互相转换,互相验证:

for i in range(0, 720368547758, 900025):
    a = str(i)
    ch_num_str = num2Chinese(a)
    b = Chinese2Num(ch_num_str)
    if a != b:
        print(a, ch_num_str, b)
        break
else:
    print("未发现转换错误!")

经过半分钟的时间,已经初步互相验证完成,未发现转换失败的数据。

彩蛋:windows文件名排序

如何快速实现windows文件名的排序效果呢?示例代码:

files = os.listdir()
files.sort(key=lambda s: [(s, int(n)) for s, n in re.findall('(\D+)(\d+)', f'a{
      s}0')])

要支持能够根据中文大写数字排序,也只需要先将大写数字都转换为普通数字再用上述方法排序即可。

原网站

版权声明
本文为[小小明-代码实体]所创,转载请带上原文链接,感谢
https://xxmdmst.blog.csdn.net/article/details/126236369