贴个Python
实现(简化了负数、小数):
import re
from itertools import chain
def convert_to_chinese(n: int):
if n <= 0: return '零'
s = ''.join(chain.from_iterable(zip('%029d' % n, 'JCBAICBAHCBAGCBAFCBAECBADCBA '))).rstrip()
b = re.sub(
r'^[^1-9]*|(?P<z1>0)[0ABC]*(?:(?=[1-9])|(?P<_z1>(?=[D-J]|$)))|'
r'(?P<b>[D-J])(?P<z2>0)[0A-L]*(?:(?=[1-9])|(?P<_z2>$))',
lambda m: f"{m['b'] or ''}{m['_z2'] is None and m['z2'] or ''}" or (m['_z1'] is None and m['z1'] or ''),
s,
)
r = re.sub('.', lambda m: '零一二三四五六七八九空空空空空空空十百千万亿兆京垓秭穰'[ord(m[0]) - ord('0')], b)
return r
if __name__ == '__main__':
for i in 100, 101, 120217, 812368, 1024842940147:
print(i, convert_to_chinese(i))
输出:
100 一百
101 一百零一
120217 一十二万零二百一十七
812368 八十一万二千三百六十八
1024842940147 一兆零二百四十八亿四千二百九十四万零一百四十七
参考了网上大佬的C#
代码(如下),
然而Python
的正则库不支持平衡组(看下PHP
手册,好像也不支持),也不支持重复命名组,所以上面代码要额外写逻辑判断来模拟
public static string ConvertToChinese(decimal number)
{
string s = number.ToString("#L#E#D#C#K#E#D#C#J#E#D#C#I#E#D#C#H#E#D#C#G#E#D#C#F#E#D#C#.@");
string d = Regex.Replace(s, @"(((?<=-)|(?!-)^)[^1-9]*)|((?'z'0)[0A-E]*((?=[1-9])|(?'-z'(?=[F-L\.]|$))))|((?'b'[F-L])(?'z'0)[0A-L]*((?=[1-9])|(?'-z'(?=[\.]|$))))", "${b}${z}");
string r = Regex.Replace(d, ".", m => "负元空零壹贰叁肆伍陆柒捌玖空空空空空空整分角拾佰仟万亿兆京垓秭穰"[m.Value[0] - '-'].ToString());
return r;
}
function toChineseNum($num) {
$cnums = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
$units = ["十", "百", "千", "万", "十", "百", "千", "亿"];
$xs = array();
foreach (array_reverse(str_split($num)) as $x) {
$l = count($xs);
if ($x != "0" || !($l % 4)) $n = ($x == '0'?'':$x) . ($units[($l-1) % count($units)]);
else $n = is_numeric($xs[0][0])?$x:'';
array_unshift($xs, $n);
}
return implode("", str_replace(array_keys($cnums), $cnums, $xs));
}
$num = 1024842940147 ;
echo toChineseNum($num);
[22-04-28] 更新:反正可读性贼差,再精简一点儿来提升5%~10%速度
[22-04-27] 更新:加上注释,怕以后看不懂
@无期徒刑,实现了个看起来别扭的PHP
版本(不熟PHP
,可能还有优化空间)
const mini_units = ['', '十', '百', '千'];
const units = ['', '万', '亿', '兆', '京', '垓', '秭', '穰'];
const nums = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
function convert_to_chinese(int $n): string
{
if ($n <= 0) return '零';
$r = '';
$d = strrev((string)$n); // 反转字串,使得低位数字也是小索引值
for ($l = strlen($d) - 1, $i = $l; $i >= 0;) {
// 输出当前位置“数字”和“单位”。开头“一十”和“0”只输出单位,个位不输出单位
$r .= nums[(int)$d[$i]] . ($i & 3 ? mini_units[$i & 3] : units[$i >> 2]);
// 万/亿/兆…时,跳过后续的“0”(若有),停留在第一个非“0”处 或 结束处,在下次循环输出该处数字和单位
if (!($i & 3) && $d[$i - 1] === '0')
// 只要结束前碰到非“0”,就将前面连续跳过的“0”,替换为这一个“零”
while (--$i >= 0 && ($d[$i] === '0' || !($r .= '零')));
// 由此处驱动至下一个数字。跳过后续的“0”百(万/亿/兆/…)、“0”十(万/亿/兆/…),
// 停留在第一个非“0”处 或 个/万/亿/兆/…处(最早出现为准),在下次循环输出该处数字和单位
elseif (--$i >= 0 && $d[$i] === '0')
// 停留在“0”个/万/亿/兆/…时不会输出“零”
while ($i & 3 && ($d[--$i] === '0' || !($r .= '零')));
}
return $r;
}
while ($s = fgets(STDIN)) {
$n = (int)$s;
printf("%d %s\n", $n, convert_to_chinese($n));
}
输出如下(总计65536
个数字,预先缓存至文件应该还能再快些。另外,WSL
这个时间看起来怪怪的):
$ time python3 -c "for i in range(1 << 16): print(f'{i:b}')" | php main.php | cat -n | sed $'1,6p; 1h; 2,5{H;d}; 6{H;g;i\\\n...\n}; $q; N; D'
1 0 零
2 1 一
3 10 一十
4 11 一十一
5 100 一百
6 101 一百零一
...
65531 1111111111111010 一千一百一十一兆一千一百一十一亿一千一百一十一万一千零一十
65532 1111111111111011 一千一百一十一兆一千一百一十一亿一千一百一十一万一千零一十一
65533 1111111111111100 一千一百一十一兆一千一百一十一亿一千一百一十一万一千一百
65534 1111111111111101 一千一百一十一兆一千一百一十一亿一千一百一十一万一千一百零一
65535 1111111111111110 一千一百一十一兆一千一百一十一亿一千一百一十一万一千一百一十
65536 1111111111111111 一千一百一十一兆一千一百一十一亿一千一百一十一万一千一百一十一
real 0m0.461s
user 0m0.406s
sys 0m0.656s
[22-04-28] 修改以配合上面的精简
改成这样,可以将开头的一十
变为十
,如一十二
变为十二
// 输出当前位置“数字”和“单位”。开头“一十”和“0”只输出单位,个位不输出单位
$r .= ($i < $l || ($i & 3) !== 1 || $d[$i] !== '1' ? nums[(int)$d[$i]] : '') . ($i & 3 ? mini_units[$i & 3] : units[$i >> 2]);
测试通过
$ diff <(python3 main.py <nums.txt | sed 's/ 一十/ 十/') <(php main.php <nums.txt)
$
“二”和“两”没找到确切规律,就不改了
一般在“千、万、亿”前用“两”
二/两百亿零两千零二/两万?
@无期徒刑,用PHP
实现了那段正则,还好PHP
支持重复命名组,比Python
省了很多事
define('unnamed', mb_str_split('零一二三四五六七八九空空空空空空空十百千万亿兆京垓秭穰'));
function convert_to_chinese_by_regex(int $n): string
{
if ($n <= 0) return '零';
$i = 0;
$n = sprintf('%029d', $n);
$s = preg_replace_callback(
'/#/',
function ($m) use ($n, &$i) {return $n[$i++];},
'#J#C#B#A#I#C#B#A#H#C#B#A#G#C#B#A#F#C#B#A#E#C#B#A#D#C#B#A#',
);
$d = preg_replace_callback(
'/(?J)^[^1-9]*|(?<z>0)[0ABC]*(?:(?=[1-9])|(?<z>(?=[D-J]|$)))|'.
'(?<b>[D-J])(?<z>0)[0A-L]*(?:(?=[1-9])|(?<z>$))/',
fn($m) => ($m['b'] ?? '') . ($m['z'] ?? ''),
$s,
);
$r = preg_replace_callback(
'/./',
fn($m) => unnamed[ord($m[0]) - ord('0')],
$d,
);
return $r;
}
但用时不是很可观,是纯数组版本耗时四五倍左右。。
$ time php main.php < 65536_nums.txt >/dev/null
real 0m2.182s
user 0m2.016s
sys 0m0.172s
之前几个数字的测试:
$ xargs -n1 <<<"100 101 120217 812368 1024842940147 $(((1 << 63) - 1))" | php main.php
100 一百
101 一百零一
120217 一十二万零二百一十七
812368 八十一万二千三百六十八
1024842940147 一兆零二百四十八亿四千二百九十四万零一百四十七
9223372036854775807 九百二十二京三千三百七十二兆零三百六十八亿五千四百七十七万五千八百零七
和Python
版输出一致:
$ diff <(python3 main.py <65536_nums.txt) <(php main.php <65536_nums.txt)
$
看看别人的处理呢
https://packagist.org/packages/bvtvd/number-conversion?query=%E6%95%B0%E5%AD%97
小米MIX2s(白)