1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
//! # Chinese Numerals
//!
//! Converts primitive integers and [big integers](num_bigint) to [Chinese numerals](https://en.wikipedia.org/wiki/Chinese_numerals).
//!
//! 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.
//!
//! ## Usage
//!
//! Add to `Cargo.toml`:
//! ```toml
//! [dependencies]
//! chinese-numerals = "0.2"
//! ```
//!
//! 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:
//!
//! - [`to_lowercase`](crate::ChineseNumeral::to_lowercase)
//! - [`to_lowercase_simp`](crate::ChineseNumeral::to_lowercase_simp)
//! - [`to_lowercase_trad`](crate::ChineseNumeral::to_lowercase_trad)
//! - [`to_uppercase`](crate::ChineseNumeral::to_uppercase)
//! - [`to_uppercase_simp`](crate::ChineseNumeral::to_uppercase_simp)
//! - [`to_uppercase_trad`](crate::ChineseNumeral::to_uppercase_trad)
//!
//! ## Premitive Integers
//!
//! For each scale, a struct has been implemented to perform the convertion.
//!
//! [`ShortScaleInt`] has implemented [`From`] trait for `i8`, `u8`, `i16`, `u16`, `i32`, and `u32`, and [`TryFrom`] trait for `i64`, `u64`, `i128`, `u128`, `isize`, and `usize`.
//!
//! [`MyriadScaleInt`], [`MidScaleInt`], and [`LongScaleInt`] have implemented `From` trait for all premitive integers.
//!
//! ### Examples
//! ```
//! use chinese_numerals::{ChineseNumeral, ShortScaleInt, MidScaleInt};
//!
//! let num = ShortScaleInt::from(1_0203_0405);
//! assert_eq!("一垓零二兆零三万零四百零五", format!("{}", num));
//! assert_eq!("壹垓零贰兆零叁万零肆佰零伍", format!("{:#}", num));
//!
//! let num = MidScaleInt::from(1_0203_0405);
//! assert_eq!("一億零二百零三萬零四百零五", num.to_lowercase_trad());
//! assert_eq!("壹億零貳佰零叄萬零肆佰零伍", num.to_uppercase_trad());
//! ```
//!
//! ## Big Integers
//!
//! For scales except short scale, a struct has been implemented to perform the convertion from [`BigInt`](num_bigint::BigInt) and [`BigUint`](num_bigint::BigUint).
//!
//! [`MyriadScaleBigInt`], [`MidScaleBigInt`], and [`LongScaleBigInt`] have implemented `TryFrom` trait for both `BigInt` and `BigUint`.
//!
//! ### Dependencies
//!
//! To enable `bigint` feature, set dependencies in `Cargo.toml`:
//! ```toml
//! [dependencies]
//! num-bigint = "0.4"
//! chinese-numerals = { version = "0.2", features = ["bigint"] }
//! ```
//!
//! ### Examples
//! ```
//! use chinese_numerals::{ChineseNumeral, LongScaleBigInt};
//! use num_bigint::BigUint;
//!
//! // 130_5480_5271_5637_0597_2964
//! let num = BigUint::new(vec![463665380, 3016835882, 707]);
//! let num = LongScaleBigInt::try_from(num).expect("Out of range");
//!
//! assert_eq!(
//!     "一百三十万五千四百八十兆五千二百七十一万\
//!     五千六百三十七亿零五百九十七万二千九百六十四",
//!     num.to_lowercase_simp()
//! );
//! ```

mod characters;
mod longscale;
mod macros;
mod midscale;
mod myriadscale;
mod shortscale;

use characters::NumChar;
pub use longscale::LongScaleInt;
pub use midscale::MidScaleInt;
pub use myriadscale::MyriadScaleInt;
pub use shortscale::ShortScaleInt;

#[cfg(feature = "bigint")]
use num_bigint::BigUint;

#[cfg(feature = "bigint")]
pub use longscale::LongScaleBigInt;
#[cfg(feature = "bigint")]
pub use midscale::MidScaleBigInt;
#[cfg(feature = "bigint")]
pub use myriadscale::MyriadScaleBigInt;

pub(crate) mod sealed {
    #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug)]
    pub enum Sign {
        Neg,
        Nil,
        Pos,
    }

    impl Default for Sign {
        fn default() -> Self {
            Self::Nil
        }
    }

    pub trait Signed {
        type Data;

        fn sign(&self) -> Sign;
        fn data(&self) -> &Self::Data;
    }

    pub trait ChineseNumeralBase: Signed {
        fn to_chars(&self) -> Vec<crate::characters::NumChar>;
        fn to_chars_trimmed(&self) -> Vec<crate::characters::NumChar>;
    }
}

use sealed::{ChineseNumeralBase, Sign, Signed};

/// Chinese variants.
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Variant {
    /// Simplified Chinese. Used in China, Singapore, and Malaysia.
    Simplified,
    /// Traditional Chinese. Used in Taiwan (Province of China), Hong Kong, and Macau.
    Traditional,
}

/// Out of range errors.
#[cfg(feature = "bigint")]
#[derive(Debug)]
pub enum Error {
    ShortScaleOutOfRange(u128),
    MyriadScaleOutOfRange(BigUint),
    MidScaleOutOfRange(BigUint),
    LongScaleOutOfRange(BigUint),
}

#[cfg(feature = "bigint")]
impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Error::ShortScaleOutOfRange(value) => write!(
                f,
                "Absolute value {value} out of range for a short scale number"
            ),
            Error::MyriadScaleOutOfRange(value) => write!(
                f,
                "Absolute value {value} out of range for a myriad scale number"
            ),
            Error::MidScaleOutOfRange(value) => write!(
                f,
                "Absolute value {value} out of range for a mid-scale number"
            ),
            Error::LongScaleOutOfRange(value) => write!(
                f,
                "Absolute value {value} out of range for a long scale number"
            ),
        }
    }
}

#[cfg(not(feature = "bigint"))]
#[derive(Debug)]
pub enum Error {
    ShortScaleOutOfRange(u128),
}

#[cfg(not(feature = "bigint"))]
impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match *self {
            Error::ShortScaleOutOfRange(value) => write!(
                f,
                "Absolute value {value} out of range for a short scale number"
            ),
        }
    }
}

impl std::error::Error for Error {}

/// Provides methods to generate Chinease numeral expression for a number.
pub trait ChineseNumeral {
    /// Converts the number to lowercase (小写数字, used for normal contexts).
    fn to_lowercase(&self, variant: Variant) -> String;

    /// Converts the number to lowercase (小写数字, used for normal contexts) in simplified Chinese.
    fn to_lowercase_simp(&self) -> String {
        self.to_lowercase(Variant::Simplified)
    }

    /// Converts the number to lowercase (小写数字, used for normal contexts) in traditional Chinese.
    fn to_lowercase_trad(&self) -> String {
        self.to_lowercase(Variant::Traditional)
    }

    /// Converts the number to uppercase (大写数字, used for financial contexts).
    fn to_uppercase(&self, variant: Variant) -> String;

    /// Converts the number to uppercase (大写数字, used for financial contexts) in simplified Chinese.
    fn to_uppercase_simp(&self) -> String {
        self.to_uppercase(Variant::Simplified)
    }

    /// Converts the number to uppercase (大写数字, used for financial contexts) in traditional Chinese.
    fn to_uppercase_trad(&self) -> String {
        self.to_uppercase(Variant::Traditional)
    }
}

impl<T: ChineseNumeralBase> ChineseNumeral for T {
    fn to_lowercase(&self, variant: Variant) -> String {
        let method = match variant {
            Variant::Simplified => NumChar::to_lowercase_simp,
            Variant::Traditional => NumChar::to_lowercase_trad,
        };
        let mut chars = self.to_chars_trimmed();
        match self.sign() {
            Sign::Neg => chars.push(NumChar::Neg),
            Sign::Nil => chars.push(NumChar::Zero),
            _ => {}
        }
        chars.into_iter().rev().map(method).collect()
    }

    fn to_uppercase(&self, variant: Variant) -> String {
        let method = match variant {
            Variant::Simplified => NumChar::to_uppercase_simp,
            Variant::Traditional => NumChar::to_uppercase_trad,
        };
        let mut chars = self.to_chars();
        match self.sign() {
            Sign::Neg => chars.push(NumChar::Neg),
            Sign::Nil => chars.push(NumChar::Zero),
            _ => {}
        }
        chars.into_iter().rev().map(method).collect()
    }
}