在日常生活中,我们经常需要将数字转换为文字形式,比如在支票上书写金额、语音播报系统或者教育软件中。今天我们要探讨的就是这样一个有趣的问题:如何用Rust将数字转换为英文表达方式。
问题背景
将数字转换为文字看似简单,但实际上涉及许多复杂的规则。英语中数字的表达有特定的规律,比如:
- 0-19有各自独特的单词
- 20-99有十位数词缀加上个位数
- 100以上则需要考虑百位、千位、百万位等
我们的任务是实现这样一个函数:
rust
pub fn encode(n: u64) -> String {
unimplemented!("Say {} in English.", n);
}
这个函数接收一个[u64]类型的数字,返回其英文表达的[String]。
英文数字表达规则
在实现之前,让我们先了解英语中数字表达的基本规则:
- 0-19: zero, one, two, three, ..., nineteen
- 20-99: twenty, thirty, forty, ..., ninety,复合数用连字符连接,如twenty-one
- 100-999: 在百位后加"hundred",如one hundred, two hundred thirty-four
- 更大数字: thousand(千), million(百万), billion(十亿), trillion(万亿)等
需要注意的是,美式英语中通常不在百位和十位之间使用"and",这与英式英语有所不同。
解决方案
解决这个问题的关键是分而治之,我们将大数字分解成较小的部分,分别处理后再组合起来。
rust
pub fn encode(n: u64) -> String {
if n == 0 {
return "zero".to_string();
}
let mut result = String::new();
let mut num = n;
// 定义各个数量级的名称
let scales = [
(1_000_000_000_000_000_000, "quintillion"),
(1_000_000_000_000_000, "quadrillion"),
(1_000_000_000_000, "trillion"),
(1_000_000_000, "billion"),
(1_000_000, "million"),
(1_000, "thousand"),
];
// 处理大于等于1000的部分
for &(scale_value, scale_name) in scales.iter() {
if num >= scale_value {
let count = num / scale_value;
result.push_str(&format_number_under_thousand(count));
result.push(' ');
result.push_str(scale_name);
num %= scale_value;
if num > 0 {
result.push(' ');
}
}
}
// 处理剩余的小于1000的部分
if num > 0 {
result.push_str(&format_number_under_thousand(num));
}
result
}
// 处理小于1000的数字
fn format_number_under_thousand(n: u64) -> String {
let mut result = String::new();
// 处理百位
if n >= 100 {
result.push_str(&format_small_numbers(n / 100));
result.push_str(" hundred");
if n % 100 != 0 {
result.push(' ');
}
}
// 处理十位和个位
if n % 100 != 0 {
result.push_str(&format_tens(n % 100));
}
result
}
// 处理0-19的数字
fn format_small_numbers(n: u64) -> &'static str {
match n {
0 => "zero",
1 => "one",
2 => "two",
3 => "three",
4 => "four",
5 => "five",
6 => "six",
7 => "seven",
8 => "eight",
9 => "nine",
10 => "ten",
11 => "eleven",
12 => "twelve",
13 => "thirteen",
14 => "fourteen",
15 => "fifteen",
16 => "sixteen",
17 => "seventeen",
18 => "eighteen",
19 => "nineteen",
_ => unreachable!(),
}
}
// 处理20-99的数字
fn format_tens(n: u64) -> String {
if n < 20 {
return format_small_numbers(n).to_string();
}
let tens_place = n / 10;
let ones_place = n % 10;
let tens_word = match tens_place {
2 => "twenty",
3 => "thirty",
4 => "forty",
5 => "fifty",
6 => "sixty",
7 => "seventy",
8 => "eighty",
9 => "ninety",
_ => unreachable!(),
};
if ones_place == 0 {
tens_word.to_string()
} else {
format!("{}-{}", tens_word, format_small_numbers(ones_place))
}
}
测试案例详解
通过查看测试案例,我们可以更好地理解问题的各种情况:
rust
#[test]
fn test_zero() {
assert_eq!(say::encode(0), String::from("zero"));
}
最基本的情况,数字0应该返回"zero"。
rust
#[test]
fn test_fourteen() {
assert_eq!(say::encode(14), String::from("fourteen"));
}
14属于0-19范围内的特殊数字。
rust
#[test]
fn test_twenty_two() {
assert_eq!(say::encode(22), String::from("twenty-two"));
}
22这样的复合数需要用连字符连接。
rust
#[test]
fn test_one_hundred_twenty_three() {
assert_eq!(say::encode(123), String::from("one hundred twenty-three"));
}
三位数需要正确处理百位和其他位之间的空格。
rust
#[test]
fn test_987654321123() {
assert_eq!(
say::encode(987_654_321_123),
String::from(
"nine hundred eighty-seven billion \
six hundred fifty-four million \
three hundred twenty-one thousand \
one hundred twenty-three"
)
);
}
更大的数字需要正确处理billion、million、thousand等单位。
rust
#[test]
fn test_max_u64() {
assert_eq!(
say::encode(18_446_744_073_709_551_615),
String::from(
"eighteen quintillion four hundred forty-six \
quadrillion seven hundred forty-four trillion \
seventy-three billion seven hundred nine million \
five hundred fifty-one thousand six hundred fifteen"
)
);
}
最大的u64值也需要能够正确处理。
设计思路解析
这个实现采用了一种模块化的方法:
- 主函数[encode]: 负责整体流程控制,按数量级分解数字
- [format_number_under_thousand]: 处理小于1000的数字
- [format_small_numbers]: 处理0-19的基础数字
- [format_tens]: 处理20-99的两位数
这种分层设计使代码更清晰易懂,也更容易维护和扩展。
Rust语言特性运用
在这个实现中,我们运用了多种Rust语言特性:
- 模式匹配: 使用[match]表达式处理不同的数字范围
- 字符串格式化: 使用[format!]宏构建字符串
- 静态字符串引用: 对于固定的词汇返回[&'static str]
- 类型安全: 利用Rust的类型系统确保不会传入负数
实际应用场景
数字转文字在很多场景下都非常有用:
- 金融系统: 支票、合同等正式文档中的金额书写
- 语音助手: 数字的语音播报
- 教育软件: 数学教学工具
- 无障碍技术: 为视障人士提供数字朗读服务
- 报表生成: 正式的报告和文档
总结
通过这个练习,我们学习到了:
- 如何将复杂问题分解为更小的子问题
- 如何处理自然语言中的规则和特殊情况
- Rust中字符串处理的最佳实践
- 模块化设计的重要性
这个问题虽然看起来简单,但涉及许多细节处理,是锻炼编程思维和Rust技能的好题目。通过合理的设计和实现,我们能够构建出一个健壮、高效的数字转文字系统。