Skip to main content

chinese_num/
lib.rs

1//! Convert a decimal number to its Chinese form.
2//!
3//! [![Build Status](https://travis-ci.org/lilydjwg/chinese-num.svg)](https://travis-ci.org/lilydjwg/chinese-num)
4//! [![Crates.io Version](https://img.shields.io/crates/v/chinese-num.svg)](https://crates.io/crates/chinese-num)
5//! [![GitHub stars](https://img.shields.io/github/stars/lilydjwg/chinese-num.svg?style=social&label=Star)](https://github.com/lilydjwg/chinese-num)
6//!
7//!
8//! # Examples
9//!
10//! ```
11//! let s = chinese_num::to_chinese_num("121").unwrap();
12//! assert_eq!(s, "一百二十一");
13//! ```
14//!
15//! ```
16//! let s = chinese_num::to_chinese_num("1004000007000500").unwrap();
17//! assert_eq!(s, "一千零四万亿零七百万零五百");
18//! ```
19//!
20//! ```
21//! let s = chinese_num::to_chinese_num("123000520").unwrap();
22//! assert_eq!(s, "一亿二千三百万零五百二十");
23//! ```
24//!
25//! ```
26//! let s = chinese_num::to_chinese_num("1234070000123780000087006786520988800000").unwrap();
27//! assert_eq!(s, "一千二百三十四万零七百亿零一十二万三千七百八十亿零八千七百亿六千七百八十六万五千二百零九亿八千八百八十万");
28//! ```
29//!
30//! If the given string is not a number, or begins with "0", `None` is returned:
31//!
32//! ```
33//! let s = chinese_num::to_chinese_num("不是数字");
34//! assert!(s.is_none());
35//! ```
36//!
37//! ```
38//! let s = chinese_num::to_chinese_num("020");
39//! assert!(s.is_none());
40//! ```
41//!
42//! The algorithm is taken from here:
43//! http://zhuanlan.zhihu.com/iobject/20370983.
44
45const DIGITS: &'static str = "零一二三四五六七八九";
46const TENS_NAME: &'static str = "个十百千";
47const UNIT_RANK: &'static str = "个十百千万亿";
48
49fn digit_pos_to_name(pos: usize) -> char {
50  if pos == 0 {
51    '个'
52  } else if pos % 8 == 0 {
53    '亿'
54  } else if pos % 4 == 0 {
55    '万'
56  } else {
57    TENS_NAME.chars().nth(pos % 4).unwrap()
58  }
59}
60
61struct ResultS (String, bool, char);
62
63fn append_digit(result: ResultS, tuple: (usize, char)) -> ResultS {
64  let (digit, this_unit) = tuple;
65  let ResultS(mut result, pending_zero, last_unit) = result;
66  let this_str = DIGITS.chars().nth(digit).unwrap();
67  if digit == 0 {
68    if UNIT_RANK.find(last_unit).unwrap() > UNIT_RANK.find(this_unit).unwrap() {
69      ResultS(result, true, last_unit)
70    } else {
71      result.push(this_unit);
72      ResultS(result, false, this_unit)
73    }
74  } else {
75    if pending_zero {
76      result.push('零');
77    }
78    result.push(this_str);
79    result.push(this_unit);
80    ResultS(result, false, this_unit)
81  }
82}
83
84pub fn to_chinese_num<N: AsRef<str>>(n: N) -> Option<String> {
85  let n = n.as_ref();
86
87  // special cases
88  if n == "0" {
89    return Some("零".to_owned());
90  }
91
92  // non-digit found, nothing, leading zeros
93  if !n.chars().all(|x| x.is_digit(10)) || n.len() == 0
94    || n.chars().nth(0).unwrap() == '0' {
95    return None;
96  }
97
98  let v = n.as_bytes().iter().rev().enumerate().map(
99    |(i, c)| ((c - '0' as u8) as usize, digit_pos_to_name(i)))
100    .rev().fold(ResultS(String::new(), false, '个'), append_digit);
101
102  let mut r = v.0;
103  if r.chars().last().unwrap() == '个' {
104    r.pop();;
105  }
106  if r.starts_with("一十") {
107    r.remove(0);
108  }
109  Some(r)
110}
111
112pub trait ToChineseNum {
113  /// A trait adding a `to_chinese_num` method to types, e.g.:
114  ///
115  /// ```
116  /// use chinese_num::ToChineseNum;
117  ///
118  /// assert_eq!(20.to_chinese_num(), Some(String::from("二十")));
119  /// ```
120  fn to_chinese_num(&self) -> Option<String>;
121}
122
123impl ToChineseNum for usize {
124  fn to_chinese_num(&self) -> Option<String> {
125    to_chinese_num(self.to_string())
126  }
127}
128
129#[test]
130fn empty_number() {
131  let s = to_chinese_num("");
132  assert!(s.is_none());
133}
134
135#[test]
136fn num_0() {
137  let s = to_chinese_num("0").unwrap();
138  assert_eq!(s, "零");
139}
140
141#[test]
142fn num_1() {
143  let s = to_chinese_num("1").unwrap();
144  assert_eq!(s, "一");
145}
146
147#[test]
148fn num_10() {
149  let s = to_chinese_num("10").unwrap();
150  assert_eq!(s, "十");
151}
152
153#[test]
154fn num_12() {
155  let s = to_chinese_num("12").unwrap();
156  assert_eq!(s, "十二");
157}
158
159#[test]
160fn num_20() {
161  let s = to_chinese_num("20").unwrap();
162  assert_eq!(s, "二十");
163}