chinese_numerals/lib.rs
1//! # Chinese Numerals
2//!
3//! Converts primitive integers and [big integers](num_bigint) to [Chinese numerals](https://en.wikipedia.org/wiki/Chinese_numerals).
4//!
5//! According to 《[五经算术](https://zh.wikipedia.org/wiki/%E4%BA%94%E7%B6%93%E7%AE%97%E8%A1%93)》, for representing numbers larger than 1,0000, there have been ten names (亿, 兆, 京, 垓, 秭, 壤, 沟, 涧, 正, and 载) and three systems (下数 short scale, 中数 mid-scale, 上数 long scale). Plus the myriad scale, in which each name represents a number 1,0000 times the previous, this crate can convert integers to four scales.
6//!
7//! ## Usage
8//!
9//! Add to `Cargo.toml`:
10//! ```toml
11//! [dependencies]
12//! chinese-numerals = "0.2"
13//! ```
14//!
15//! All structs have implemented [`Display`](std::fmt::Display) trait's normal (with `"{}"`) and alternative (with `"{:#}"`) formats, converting to lowercase and uppercase Chinese numbers. Besides, [`ChineseNumeral`] trait provides following functions:
16//!
17//! - [`to_lowercase`](crate::ChineseNumeral::to_lowercase)
18//! - [`to_lowercase_simp`](crate::ChineseNumeral::to_lowercase_simp)
19//! - [`to_lowercase_trad`](crate::ChineseNumeral::to_lowercase_trad)
20//! - [`to_uppercase`](crate::ChineseNumeral::to_uppercase)
21//! - [`to_uppercase_simp`](crate::ChineseNumeral::to_uppercase_simp)
22//! - [`to_uppercase_trad`](crate::ChineseNumeral::to_uppercase_trad)
23//!
24//! ## Premitive Integers
25//!
26//! For each scale, a struct has been implemented to perform the convertion.
27//!
28//! [`ShortScaleInt`] has implemented [`From`] trait for `i8`, `u8`, `i16`, `u16`, `i32`, and `u32`, and [`TryFrom`] trait for `i64`, `u64`, `i128`, `u128`, `isize`, and `usize`.
29//!
30//! [`MyriadScaleInt`], [`MidScaleInt`], and [`LongScaleInt`] have implemented `From` trait for all premitive integers.
31//!
32//! ### Examples
33//! ```
34//! use chinese_numerals::{ChineseNumeral, ShortScaleInt, MidScaleInt};
35//!
36//! let num = ShortScaleInt::from(1_0203_0405);
37//! assert_eq!("一垓零二兆零三万零四百零五", format!("{}", num));
38//! assert_eq!("壹垓零贰兆零叁万零肆佰零伍", format!("{:#}", num));
39//!
40//! let num = MidScaleInt::from(1_0203_0405);
41//! assert_eq!("一億零二百零三萬零四百零五", num.to_lowercase_trad());
42//! assert_eq!("壹億零貳佰零叄萬零肆佰零伍", num.to_uppercase_trad());
43//! ```
44//!
45//! ## Big Integers
46//!
47//! For scales except short scale, a struct has been implemented to perform the convertion from [`BigInt`](num_bigint::BigInt) and [`BigUint`](num_bigint::BigUint).
48//!
49//! [`MyriadScaleBigInt`], [`MidScaleBigInt`], and [`LongScaleBigInt`] have implemented `TryFrom` trait for both `BigInt` and `BigUint`.
50//!
51//! ### Dependencies
52//!
53//! To enable `bigint` feature, set dependencies in `Cargo.toml`:
54//! ```toml
55//! [dependencies]
56//! num-bigint = "0.4"
57//! chinese-numerals = { version = "0.2", features = ["bigint"] }
58//! ```
59//!
60//! ### Examples
61//! ```
62//! use chinese_numerals::{ChineseNumeral, LongScaleBigInt};
63//! use num_bigint::BigUint;
64//!
65//! // 130_5480_5271_5637_0597_2964
66//! let num = BigUint::new(vec![463665380, 3016835882, 707]);
67//! let num = LongScaleBigInt::try_from(num).expect("Out of range");
68//!
69//! assert_eq!(
70//! "一百三十万五千四百八十兆五千二百七十一万\
71//! 五千六百三十七亿零五百九十七万二千九百六十四",
72//! num.to_lowercase_simp()
73//! );
74//! ```
75
76mod characters;
77mod longscale;
78mod macros;
79mod midscale;
80mod myriadscale;
81mod shortscale;
82
83use characters::NumChar;
84pub use longscale::LongScaleInt;
85pub use midscale::MidScaleInt;
86pub use myriadscale::MyriadScaleInt;
87pub use shortscale::ShortScaleInt;
88
89#[cfg(feature = "bigint")]
90use num_bigint::BigUint;
91
92#[cfg(feature = "bigint")]
93pub use longscale::LongScaleBigInt;
94#[cfg(feature = "bigint")]
95pub use midscale::MidScaleBigInt;
96#[cfg(feature = "bigint")]
97pub use myriadscale::MyriadScaleBigInt;
98
99pub(crate) mod sealed {
100 #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug)]
101 pub enum Sign {
102 Neg,
103 Nil,
104 Pos,
105 }
106
107 impl Default for Sign {
108 fn default() -> Self {
109 Self::Nil
110 }
111 }
112
113 pub trait Signed {
114 type Data;
115
116 fn sign(&self) -> Sign;
117 fn data(&self) -> &Self::Data;
118 }
119
120 pub trait ChineseNumeralBase: Signed {
121 fn to_chars(&self) -> Vec<crate::characters::NumChar>;
122 fn to_chars_trimmed(&self) -> Vec<crate::characters::NumChar>;
123 }
124}
125
126use sealed::{ChineseNumeralBase, Sign, Signed};
127
128/// Chinese variants.
129#[derive(PartialEq, Eq, Clone, Copy, Debug)]
130pub enum Variant {
131 /// Simplified Chinese. Used in China, Singapore, and Malaysia.
132 Simplified,
133 /// Traditional Chinese. Used in Taiwan (Province of China), Hong Kong, and Macau.
134 Traditional,
135}
136
137/// Out of range errors.
138#[cfg(feature = "bigint")]
139#[derive(Debug)]
140pub enum Error {
141 ShortScaleOutOfRange(u128),
142 MyriadScaleOutOfRange(BigUint),
143 MidScaleOutOfRange(BigUint),
144 LongScaleOutOfRange(BigUint),
145}
146
147#[cfg(feature = "bigint")]
148impl std::fmt::Display for Error {
149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150 match self {
151 Error::ShortScaleOutOfRange(value) => write!(
152 f,
153 "Absolute value {value} out of range for a short scale number"
154 ),
155 Error::MyriadScaleOutOfRange(value) => write!(
156 f,
157 "Absolute value {value} out of range for a myriad scale number"
158 ),
159 Error::MidScaleOutOfRange(value) => write!(
160 f,
161 "Absolute value {value} out of range for a mid-scale number"
162 ),
163 Error::LongScaleOutOfRange(value) => write!(
164 f,
165 "Absolute value {value} out of range for a long scale number"
166 ),
167 }
168 }
169}
170
171#[cfg(not(feature = "bigint"))]
172#[derive(Debug)]
173pub enum Error {
174 ShortScaleOutOfRange(u128),
175}
176
177#[cfg(not(feature = "bigint"))]
178impl std::fmt::Display for Error {
179 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180 match *self {
181 Error::ShortScaleOutOfRange(value) => write!(
182 f,
183 "Absolute value {value} out of range for a short scale number"
184 ),
185 }
186 }
187}
188
189impl std::error::Error for Error {}
190
191/// Provides methods to generate Chinease numeral expression for a number.
192pub trait ChineseNumeral {
193 /// Converts the number to lowercase (小写数字, used for normal contexts).
194 fn to_lowercase(&self, variant: Variant) -> String;
195
196 /// Converts the number to lowercase (小写数字, used for normal contexts) in simplified Chinese.
197 fn to_lowercase_simp(&self) -> String {
198 self.to_lowercase(Variant::Simplified)
199 }
200
201 /// Converts the number to lowercase (小写数字, used for normal contexts) in traditional Chinese.
202 fn to_lowercase_trad(&self) -> String {
203 self.to_lowercase(Variant::Traditional)
204 }
205
206 /// Converts the number to uppercase (大写数字, used for financial contexts).
207 fn to_uppercase(&self, variant: Variant) -> String;
208
209 /// Converts the number to uppercase (大写数字, used for financial contexts) in simplified Chinese.
210 fn to_uppercase_simp(&self) -> String {
211 self.to_uppercase(Variant::Simplified)
212 }
213
214 /// Converts the number to uppercase (大写数字, used for financial contexts) in traditional Chinese.
215 fn to_uppercase_trad(&self) -> String {
216 self.to_uppercase(Variant::Traditional)
217 }
218}
219
220impl<T: ChineseNumeralBase> ChineseNumeral for T {
221 fn to_lowercase(&self, variant: Variant) -> String {
222 let method = match variant {
223 Variant::Simplified => NumChar::to_lowercase_simp,
224 Variant::Traditional => NumChar::to_lowercase_trad,
225 };
226 let mut chars = self.to_chars_trimmed();
227 match self.sign() {
228 Sign::Neg => chars.push(NumChar::Neg),
229 Sign::Nil => chars.push(NumChar::Zero),
230 _ => {}
231 }
232 chars.into_iter().rev().map(method).collect()
233 }
234
235 fn to_uppercase(&self, variant: Variant) -> String {
236 let method = match variant {
237 Variant::Simplified => NumChar::to_uppercase_simp,
238 Variant::Traditional => NumChar::to_uppercase_trad,
239 };
240 let mut chars = self.to_chars();
241 match self.sign() {
242 Sign::Neg => chars.push(NumChar::Neg),
243 Sign::Nil => chars.push(NumChar::Zero),
244 _ => {}
245 }
246 chars.into_iter().rev().map(method).collect()
247 }
248}