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}