打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

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

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

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

其实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')])

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

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
微信小程序——比较数字大小案例
华为机试HJ92:在字符串中找出连续最长的数字串
C和指针之字符串实现my_strrchr(char *str, int ch)的函数
【Go语言入门100题】039 古风排版 (20 分) Go语言 | Golang
PHP数字转换中文汉字,汉语语法问题已处理
100 个基本 Python 面试问题第三部分(41-60)
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服