From d2cb7b84a59967bbd3395e3cd2b483bd658414a5 Mon Sep 17 00:00:00 2001 From: Malo <57839069+MDLC01@users.noreply.github.com> Date: Wed, 25 Feb 2026 15:04:13 +0100 Subject: [PATCH 1/2] Use Rust 2024 --- Cargo.lock | 108 +++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 8 +++- src/lib.rs | 5 +-- src/shared.rs | 18 ++++++--- src/styling.rs | 4 +- 5 files changed, 129 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6802aad..c648bb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,12 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "base64" version = "0.22.1" @@ -35,10 +41,29 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +[[package]] +name = "chinese-number" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e964125508474a83c95eb935697abbeb446ff4e9d62c71ce880e3986d1c606b" +dependencies = [ + "chinese-variant", + "enum-ordinalize", + "num-bigint", + "num-traits", +] + +[[package]] +name = "chinese-variant" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58b52a9840ffff5d4d0058ae529fa066a75e794e3125546acfc61c23ad755e49" + [[package]] name = "codex" version = "0.2.0" dependencies = [ + "chinese-number", "ureq", ] @@ -51,6 +76,26 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "flate2" version = "1.1.2" @@ -122,6 +167,34 @@ dependencies = [ "adler2", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -134,6 +207,24 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + [[package]] name = "ring" version = "0.17.14" @@ -204,6 +295,23 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + [[package]] name = "untrusted" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index e9dc852..6fbd3f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "codex" version = "0.2.0" authors = ["The Typst Project Developers"] -edition = "2021" +edition = "2024" description = "Human-friendly notation for Unicode symbols." repository = "https://github.com/typst/codex" readme = "README.md" @@ -11,9 +11,13 @@ categories = ["encoding", "text-processing"] keywords = ["unicode", "symbols"] [features] -default = ["styling"] +default = ["numeral-systems", "styling"] +numeral-systems = ["chinese-number"] styling = [] _test-unicode-conformance = ["ureq"] +[dependencies] +chinese-number = { version = "0.7.7", default-features = false, features = ["number-to-chinese"], optional = true } + [build-dependencies] ureq = { version = "3.0.12", optional = true } diff --git a/src/lib.rs b/src/lib.rs index 38d7f51..f7e8cd9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -159,10 +159,7 @@ mod test { #[test] fn random_sample() { for (key, control) in [ - ( - "backslash", - [("", "\\"), ("not", "⧷"), ("o", "⦸")].as_slice(), - ), + ("backslash", [("", "\\"), ("not", "⧷"), ("o", "⦸")].as_slice()), ("chi", &[("", "χ")]), ("forces", &[("", "⊩"), ("not", "⊮")]), ("interleave", &[("", "⫴"), ("big", "⫼"), ("struck", "⫵")]), diff --git a/src/shared.rs b/src/shared.rs index b9bc09c..ac09d0d 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -172,12 +172,18 @@ mod tests { #[test] fn subset() { - assert!(ModifierSet::from_raw_dotted("a") - .is_subset(ModifierSet::from_raw_dotted("a.b"))); - assert!(ModifierSet::from_raw_dotted("a") - .is_subset(ModifierSet::from_raw_dotted("b.a"))); - assert!(ModifierSet::from_raw_dotted("a.b") - .is_subset(ModifierSet::from_raw_dotted("b.c.a"))); + assert!( + ModifierSet::from_raw_dotted("a") + .is_subset(ModifierSet::from_raw_dotted("a.b")) + ); + assert!( + ModifierSet::from_raw_dotted("a") + .is_subset(ModifierSet::from_raw_dotted("b.a")) + ); + assert!( + ModifierSet::from_raw_dotted("a.b") + .is_subset(ModifierSet::from_raw_dotted("b.c.a")) + ); } #[test] diff --git a/src/styling.rs b/src/styling.rs index 8783216..1e77774 100644 --- a/src/styling.rs +++ b/src/styling.rs @@ -228,8 +228,8 @@ impl MathStyle { bold: bool, italic: Option, ) -> MathStyle { - use conversions::*; use MathVariant::*; + use conversions::*; match (variant.unwrap_or(Plain), bold, italic) { (SansSerif, false, Some(false)) if is_latin(c) => MathStyle::SansSerif, (SansSerif, false, _) if is_latin(c) => MathStyle::SansSerifItalic, @@ -368,8 +368,8 @@ impl fmt::Display for ToStyle { /// assert_eq!("𝕩ℽΩ𝔸𞺸𞺧𝟙⅀𞺮", s); /// ``` pub fn to_style(c: char, style: MathStyle) -> ToStyle { - use conversions::*; use MathStyle::*; + use conversions::*; let styled = match style { Plain => [c, '\0'], Bold => [to_bold(c), '\0'], From 29ec220953c5fdd0b3f71e24f5d49698020537ac Mon Sep 17 00:00:00 2001 From: Malo <57839069+MDLC01@users.noreply.github.com> Date: Thu, 19 Feb 2026 00:18:02 +0100 Subject: [PATCH 2/2] Port numbering systems from Typst --- src/lib.rs | 3 + src/numeral_systems.rs | 1352 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1355 insertions(+) create mode 100644 src/numeral_systems.rs diff --git a/src/lib.rs b/src/lib.rs index f7e8cd9..0ad21d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,9 @@ pub use self::shared::ModifierSet; mod shared; +#[cfg(feature = "numeral-systems")] +pub mod numeral_systems; + #[cfg(feature = "styling")] pub mod styling; diff --git a/src/numeral_systems.rs b/src/numeral_systems.rs new file mode 100644 index 0000000..f9e821a --- /dev/null +++ b/src/numeral_systems.rs @@ -0,0 +1,1352 @@ +//! Various ways of displaying non-negative integers. + +use chinese_number::{ChineseCase, ChineseVariant, from_u64_to_chinese_ten_thousand}; +use std::fmt::{Display, Formatter}; + +macro_rules! declare_named { + ( + $( #[$attr:meta] )* + $vis:vis enum $Ty:ident { + $( + $( #[$vattr:meta] )* + $Value:ident = $name:literal $( ($shorthand:literal) )?, + )* + } + ) => { + $( #[$attr] )* + $vis enum $Ty { + $( + $( #[$vattr] )* + $Value, + )* + } + + impl $Ty { + /// Returns the named numeral system associated with a name, if any. + pub fn from_name(name: &str) -> Option { + match name { + $( $name => Some(Self::$Value), )* + _ => None, + } + } + + /// Returns the name of a named numeral system. + pub const fn name(self) -> &'static str { + match self { + $( Self::$Value => $name, )* + } + } + + /// Returns the named numeral system associated with a shorthand. + /// + /// A shorthand is a short string that identifies a named numeral + /// system, such as `"I"` for + /// [upper Roman numerals](Self::UpperRoman), or `"א"` for + /// [Hebrew numerals](Self::Hebrew). Usually, this is the number one + /// represented in the system. + pub fn from_shorthand(shorthand: &str) -> Option { + match shorthand { + $( $( $shorthand => Some(Self::$Value), )? )* + _ => None, + } + } + + /// Returns the optional [shorthand](Self::from_shorthand) for a + /// named numeral system. + pub fn shorthand(self) -> Option<&'static str> { + match self { + $( $( Self::$Value => Some($shorthand), )? )* + _ => None, + } + } + + /// Returns an iterator over the values of this type. + pub fn iter() -> impl Iterator { + [ + $( Self::$Value, )* + ].into_iter() + } + } + }; +} + +declare_named! { + /// A list of named numeral systems. + /// + /// The underlying system of a named numeral system can be obtained with the + /// [`system`](Self::system) method. + #[non_exhaustive] + #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] + pub enum NamedNumeralSystem { + /// Decimal positional notation using + /// [Western Arabic numerals](https://en.wikipedia.org/wiki/Arabic_numerals). + /// + /// ## Representable Numbers + /// + /// All non-negative integers can be represented. + /// + /// ## Example + /// + /// The first twelve non-negative integers are represented as follows: + /// + /// > 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 + Arabic = "arabic" ("1"), + /// Circled decimal positional notation using + /// [Western Arabic numerals](https://en.wikipedia.org/wiki/Arabic_numerals). + /// + /// ## Representable Numbers + /// + /// Non-negative integers up to and including fifty can be represented. + /// + /// ## Example + /// + /// The first twelve non-negative integers are represented as follows: + /// + /// > ⓪, ①, ②, ③, ④, ⑤, ⑥, ⑦, ⑧, ⑨, ⑩, ⑪ + CircledArabic = "arabic.o" ("①"), + /// Double circled decimal positional notation using + /// [Western Arabic numerals](https://en.wikipedia.org/wiki/Arabic_numerals). + /// + /// ## Representable Numbers + /// + /// Positive integers up to and including ten can be represented. + /// + /// ## Example + /// + /// The integers from one to ten are represented as follows: + /// + /// > ⓵, ⓶, ⓷, ⓸, ⓹, ⓺, ⓻, ⓼, ⓽, ⓾ + DoubleCircledArabic = "arabic.oo" ("⓵"), + /// Lowercase + /// [Latin letters](https://en.wikipedia.org/wiki/Latin_alphabet). + /// + /// ## Representable Numbers + /// + /// All positive integers can be represented. + /// + /// ## Example + /// + /// The first twenty-eight positive integers are represented as follows: + /// + /// > a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, + /// > w, x, y, z, aa, ab + LowerLatin = "latin" ("a"), + /// Uppercase + /// [Latin letters](https://en.wikipedia.org/wiki/Latin_alphabet). + /// + /// ## Representable Numbers + /// + /// All positive integers can be represented. + /// + /// ## Example + /// + /// The first twenty-eight positive integers are represented as follows: + /// + /// > A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, + /// > W, X, Y, Z, AA, AB + UpperLatin = "Latin" ("A"), + /// Lowercase + /// [Roman numerals](https://en.wikipedia.org/wiki/Roman_numerals). + /// + /// ## Representable Numbers + /// + /// All non-negative integers can be represented. + /// + /// ## Example + /// + /// The first twelve non-negative integers are represented as follows: + /// + /// > n, i, ii, iii, iv, v, vi, vii, viii, ix, x, xi + LowerRoman = "roman" ("i"), + /// Uppercase + /// [Roman numerals](https://en.wikipedia.org/wiki/Roman_numerals). + /// + /// ## Representable Numbers + /// + /// All non-negative integers can be represented. + /// + /// ## Example + /// + /// The first twelve non-negative integers are represented as follows: + /// + /// > N, I, II, III, IV, V, VI, VII, VIII, IX, X, XI + UpperRoman = "Roman" ("I"), + /// Lowercase + /// [Greek numerals](https://en.wikipedia.org/wiki/Greek_numerals). + /// + /// ## Representable Numbers + /// + /// All non-negative integers can be represented. + /// + /// ## Example + /// + /// The first thirteen non-negative integers are represented as follows: + /// + /// > 𐆊, α, β, γ, δ, ε, στ, ζ, η, θ, ι, ια, ιβ + LowerGreek = "greek" ("α"), + /// Uppercase + /// [Greek numerals](https://en.wikipedia.org/wiki/Greek_numerals). + /// + /// ## Representable Numbers + /// + /// All non-negative integers can be represented. + /// + /// ## Example + /// + /// The first thirteen non-negative integers are represented as follows: + /// + /// > 𐆊, Α, Β, Γ, Δ, Ε, ΣΤ, Ζ, Η, Θ, Ι, ΙΑ, ΙΒ + UpperGreek = "Greek" ("Α"), + /// Lowercase + /// [Armenian numerals](https://en.wikipedia.org/wiki/Armenian_numerals). + /// + /// ## Representable Numbers + /// + /// All positive integers can be represented. + /// + /// ## Example + /// + /// The first twelve positive integers are represented as follows: + /// + /// > ա, բ, գ, դ, ե, զ, է, ը, թ, ժ, ժա, ժբ + LowerArmenian = "armenian" ("ա"), + /// Uppercase + /// [Armenian numerals](https://en.wikipedia.org/wiki/Armenian_numerals). + /// + /// ## Representable Numbers + /// + /// All positive integers can be represented. + /// + /// ## Example + /// + /// The first twelve positive integers are represented as follows: + /// + /// > Ա, Բ, Գ, Դ, Ե, Զ, Է, Ը, Թ, Ժ, ԺԱ, ԺԲ + UpperArmenian = "Armenian" ("Ա"), + /// [Hebrew alphabetic numerals](https://en.wikipedia.org/wiki/Hebrew_numerals) + /// without a + /// [gershayim](https://en.wikipedia.org/wiki/Hebrew_numerals#Gershayim) + /// or geresh. + /// + /// ## Representable Numbers + /// + /// All positive integers can be represented. + /// + /// ## Example + /// + /// The first twelve positive integers are represented as follows: + /// + /// > א, ב, + /// > ג, ד, + /// > ה, ו, + /// > ז, ח, + /// > ט, י, + /// > יא, יב + Hebrew = "hebrew" ("א"), + /// Everyday ordinary simplified + /// [Chinese numerals](https://en.wikipedia.org/wiki/Chinese_numerals#Ordinary_numerals). + /// + /// ## Representable Numbers + /// + /// All non-negative integers can be represented. + /// + /// ## Example + /// + /// The first thirteen non-negative integers are represented as follows: + /// + /// > 零, 一, 二, 三, 四, 五, 六, 七, 八, 九, 十, 十一, 十二 + LowerSimplifiedChinese = "chinese.simple" ("一"), + /// Financial ("capital") ordinary simplified + /// [Chinese numerals](https://en.wikipedia.org/wiki/Chinese_numerals#Ordinary_numerals). + /// + /// ## Representable Numbers + /// + /// All non-negative integers can be represented. + /// + /// ## Example + /// + /// The first thirteen non-negative integers are represented as follows: + /// + /// > 零, 壹, 贰, 叁, 肆, 伍, 陆, 柒, 捌, 玖, 拾, 拾壹, 拾贰 + UpperSimplifiedChinese = "Chinese.simple" ("壹"), + /// Everyday ordinary traditional + /// [Chinese numerals](https://en.wikipedia.org/wiki/Chinese_numerals#Ordinary_numerals). + /// + /// ## Representable Numbers + /// + /// All non-negative integers can be represented. + /// + /// ## Example + /// + /// The first thirteen non-negative integers are represented as follows: + /// + /// > 零, 一, 二, 三, 四, 五, 六, 七, 八, 九, 十, 十一, 十二 + LowerTraditionalChinese = "chinese.trad", + /// Financial ("capital") ordinary traditional + /// [Chinese numerals](https://en.wikipedia.org/wiki/Chinese_numerals#Ordinary_numerals). + /// + /// ## Representable Numbers + /// + /// All non-negative integers can be represented. + /// + /// ## Example + /// + /// The first thirteen non-negative integers are represented as follows: + /// + /// > 零, 壹, 貳, 參, 肆, 伍, 陸, 柒, 捌, 玖, 拾, 拾壹, 拾貳 + UpperTraditionalChinese = "Chinese.trad", + /// Hiragana in the gojūon order. Includes n but excludes wi and we. + /// + /// ## Representable Numbers + /// + /// All positive integers can be represented. + /// + /// ## Example + /// + /// The first twelve positive integers are represented as follows: + /// + /// > あ, い, う, え, お, か, き, く, け, こ, さ, し + HiraganaAiueo = "hiragana.aiueo" ("あ"), + /// Hiragana in the iroha order. Includes wi and we but excludes n. + /// + /// ## Representable Numbers + /// + /// All positive integers can be represented. + /// + /// ## Example + /// + /// The first twelve positive integers are represented as follows: + /// + /// > い, ろ, は, に, ほ, へ, と, ち, り, ぬ, る, を + HiraganaIroha = "hiragana.iroha" ("い"), + /// Katakana in the gojūon order. Includes n but excludes wi and we. + /// + /// ## Representable Numbers + /// + /// All positive integers can be represented. + /// + /// ## Example + /// + /// The first twelve positive integers are represented as follows: + /// + /// > ア, イ, ウ, エ, オ, カ, キ, ク, ケ, コ, サ, シ + KatakanaAiueo = "katakana.aiueo" ("ア"), + /// Katakana in the iroha order. Includes wi and we but excludes n. + /// + /// ## Representable Numbers + /// + /// All positive integers can be represented. + /// + /// ## Example + /// + /// The first twelve positive integers are represented as follows: + /// + /// > イ, ロ, ハ, ニ, ホ, ヘ, ト, チ, リ, ヌ, ル, ヲ + KatakanaIroha = "katakana.iroha" ("イ"), + // TODO: Improve Korean numeral systems based on https://github.com/typst/typst/issues/7335. + /// Korean jamo. + /// + /// ## Representable Numbers + /// + /// All positive integers can be represented. + /// + /// ## Example + /// + /// The first twelve positive integers are represented as follows: + /// + /// > ㄱ, ㄴ, ㄷ, ㄹ, ㅁ, ㅂ, ㅅ, ㅇ, ㅈ, ㅊ, ㅋ, ㅌ + KoreanJamo = "korean.jamo" ("ㄱ"), + /// Korean syllables. + /// + /// ## Representable Numbers + /// + /// All positive integers can be represented. + /// + /// ## Example + /// + /// The first twelve positive integers are represented as follows: + /// + /// > 가, 나, 다, 라, 마, 바, 사, 아, 자, 차, 카, 타 + KoreanSyllable = "korean.syllable" ("가"), + /// Decimal positional notation using + /// [Eastern Arabic numerals](https://en.wikipedia.org/wiki/Eastern_Arabic_numerals#Numerals). + /// + /// ## Representable Numbers + /// + /// All non-negative integers can be represented. + /// + /// ## Example + /// + /// The first twelve non-negative integers are represented as follows: + /// + /// > ٠, ١, + /// > ٢, ٣, + /// > ٤, ٥, + /// > ٦, ٧, + /// > ٨, ٩, + /// > ١٠, ١١ + EasternArabic = "arabic.eastern" ("١"), + /// Decimal positional notation using the Persian variant of + /// [Eastern Arabic numerals](https://en.wikipedia.org/wiki/Eastern_Arabic_numerals#Numerals). + /// + /// ## Representable Numbers + /// + /// All non-negative integers can be represented. + /// + /// ## Example + /// + /// The first twelve non-negative integers are represented as follows: + /// + /// > ۰, ۱, + /// > ۲, ۳, + /// > ۴, ۵, + /// > ۶, ۷, + /// > ۸, ۹, + /// > ۱۰, ۱۱ + Persian = "persian" ("۱"), + /// Decimal positional notation using + /// [Devanagari numerals](https://en.wikipedia.org/wiki/Devanagari_numerals). + /// + /// ## Representable Numbers + /// + /// All non-negative integers can be represented. + /// + /// ## Example + /// + /// The first twelve non-negative integers are represented as follows: + /// + /// > ०, १, २, ३, ४, ५, ६, ७, ८, ९, १०, ११ + Devanagari = "devanagari" ("१"), + /// Decimal positional notation using + /// [Bengali numerals](https://en.wikipedia.org/wiki/Bengali_numerals). + /// + /// ## Representable Numbers + /// + /// All non-negative integers can be represented. + /// + /// ## Example + /// + /// The first twelve non-negative integers are represented as follows: + /// + /// > ০, ১, ২, ৩, ৪, ৫, ৬, ৭, ৮, ৯, ১০, ১১ + Bengali = "bengali" ("১"), + /// Bengali letters. + /// + /// ## Representable Numbers + /// + /// All positive integers can be represented. + /// + /// ## Example + /// + /// The first twelve positive integers are represented as follows: + /// + /// > ক, খ, গ, ঘ, ঙ, চ, ছ, জ, ঝ, ঞ, ট, ঠ + BengaliLetters = "bengali.letter" ("ক"), + /// Repeating + /// [note numbering symbols](https://en.wikipedia.org/wiki/Note_(typography)#Numbering_and_symbols). + /// + /// ## Representable Numbers + /// + /// All positive integers can be represented. + /// + /// ## Example + /// + /// The first fourteen positive integers are represented as follows: + /// + /// > *, †, ‡, §, ¶, ‖, **, ††, ‡‡, §§, ¶¶, ‖‖, ***, ††† + Symbols = "symbol" ("*"), + } +} + +impl NamedNumeralSystem { + /// Returns the underlying numeral system. + pub fn system(self) -> NumeralSystem<'static> { + match self { + Self::Arabic => NumeralSystem::Positional(&[ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + ]), + + Self::CircledArabic => NumeralSystem::Fixed(&[ + '⓪', '①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩', '⑪', '⑫', '⑬', + '⑭', '⑮', '⑯', '⑰', '⑱', '⑲', '⑳', '㉑', '㉒', '㉓', '㉔', '㉕', '㉖', + '㉗', '㉘', '㉙', '㉚', '㉛', '㉜', '㉝', '㉞', '㉟', '㊱', '㊲', '㊳', + '㊴', '㊵', '㊶', '㊷', '㊸', '㊹', '㊺', '㊻', '㊼', '㊽', '㊾', '㊿', + ]), + + Self::DoubleCircledArabic => NumeralSystem::ZerolessFixed(&[ + '⓵', '⓶', '⓷', '⓸', '⓹', '⓺', '⓻', '⓼', '⓽', '⓾', + ]), + + Self::LowerLatin => NumeralSystem::Bijective(&[ + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + ]), + + Self::UpperLatin => NumeralSystem::Bijective(&[ + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + ]), + + Self::LowerRoman => NumeralSystem::Additive(&[ + ("m̅", 1000000), + ("d̅", 500000), + ("c̅", 100000), + ("l̅", 50000), + ("x̅", 10000), + ("v̅", 5000), + ("i̅v̅", 4000), + ("m", 1000), + ("cm", 900), + ("d", 500), + ("cd", 400), + ("c", 100), + ("xc", 90), + ("l", 50), + ("xl", 40), + ("x", 10), + ("ix", 9), + ("v", 5), + ("iv", 4), + ("i", 1), + ("n", 0), + ]), + + Self::UpperRoman => NumeralSystem::Additive(&[ + ("M̅", 1000000), + ("D̅", 500000), + ("C̅", 100000), + ("L̅", 50000), + ("X̅", 10000), + ("V̅", 5000), + ("I̅V̅", 4000), + ("M", 1000), + ("CM", 900), + ("D", 500), + ("CD", 400), + ("C", 100), + ("XC", 90), + ("L", 50), + ("XL", 40), + ("X", 10), + ("IX", 9), + ("V", 5), + ("IV", 4), + ("I", 1), + ("N", 0), + ]), + + Self::LowerGreek => NumeralSystem::Additive(&[ + ("͵θ", 9000), + ("͵η", 8000), + ("͵ζ", 7000), + ("͵ϛ", 6000), + ("͵ε", 5000), + ("͵δ", 4000), + ("͵γ", 3000), + ("͵β", 2000), + ("͵α", 1000), + ("ϡ", 900), + ("ω", 800), + ("ψ", 700), + ("χ", 600), + ("φ", 500), + ("υ", 400), + ("τ", 300), + ("σ", 200), + ("ρ", 100), + ("ϟ", 90), + ("π", 80), + ("ο", 70), + ("ξ", 60), + ("ν", 50), + ("μ", 40), + ("λ", 30), + ("κ", 20), + ("ι", 10), + ("θ", 9), + ("η", 8), + ("ζ", 7), + ("στ", 6), + ("ε", 5), + ("δ", 4), + ("γ", 3), + ("β", 2), + ("α", 1), + ("𐆊", 0), + ]), + + Self::UpperGreek => NumeralSystem::Additive(&[ + ("͵Θ", 9000), + ("͵Η", 8000), + ("͵Ζ", 7000), + ("͵Ϛ", 6000), + ("͵Ε", 5000), + ("͵Δ", 4000), + ("͵Γ", 3000), + ("͵Β", 2000), + ("͵Α", 1000), + ("Ϡ", 900), + ("Ω", 800), + ("Ψ", 700), + ("Χ", 600), + ("Φ", 500), + ("Υ", 400), + ("Τ", 300), + ("Σ", 200), + ("Ρ", 100), + ("Ϟ", 90), + ("Π", 80), + ("Ο", 70), + ("Ξ", 60), + ("Ν", 50), + ("Μ", 40), + ("Λ", 30), + ("Κ", 20), + ("Ι", 10), + ("Θ", 9), + ("Η", 8), + ("Ζ", 7), + ("ΣΤ", 6), + ("Ε", 5), + ("Δ", 4), + ("Γ", 3), + ("Β", 2), + ("Α", 1), + ("𐆊", 0), + ]), + + Self::LowerArmenian => NumeralSystem::Additive(&[ + ("ք", 9000), + ("փ", 8000), + ("ւ", 7000), + ("ց", 6000), + ("ր", 5000), + ("տ", 4000), + ("վ", 3000), + ("ս", 2000), + ("ռ", 1000), + ("ջ", 900), + ("պ", 800), + ("չ", 700), + ("ո", 600), + ("շ", 500), + ("ն", 400), + ("յ", 300), + ("մ", 200), + ("ճ", 100), + ("ղ", 90), + ("ձ", 80), + ("հ", 70), + ("կ", 60), + ("ծ", 50), + ("խ", 40), + ("լ", 30), + ("ի", 20), + ("ժ", 10), + ("թ", 9), + ("ը", 8), + ("է", 7), + ("զ", 6), + ("ե", 5), + ("դ", 4), + ("գ", 3), + ("բ", 2), + ("ա", 1), + ]), + + Self::UpperArmenian => NumeralSystem::Additive(&[ + ("Ք", 9000), + ("Փ", 8000), + ("Ւ", 7000), + ("Ց", 6000), + ("Ր", 5000), + ("Տ", 4000), + ("Վ", 3000), + ("Ս", 2000), + ("Ռ", 1000), + ("Ջ", 900), + ("Պ", 800), + ("Չ", 700), + ("Ո", 600), + ("Շ", 500), + ("Ն", 400), + ("Յ", 300), + ("Մ", 200), + ("Ճ", 100), + ("Ղ", 90), + ("Ձ", 80), + ("Հ", 70), + ("Կ", 60), + ("Ծ", 50), + ("Խ", 40), + ("Լ", 30), + ("Ի", 20), + ("Ժ", 10), + ("Թ", 9), + ("Ը", 8), + ("Է", 7), + ("Զ", 6), + ("Ե", 5), + ("Դ", 4), + ("Գ", 3), + ("Բ", 2), + ("Ա", 1), + ]), + + Self::Hebrew => NumeralSystem::Additive(&[ + ("ת", 400), + ("ש", 300), + ("ר", 200), + ("ק", 100), + ("צ", 90), + ("פ", 80), + ("ע", 70), + ("ס", 60), + ("נ", 50), + ("מ", 40), + ("ל", 30), + ("כ", 20), + ("יט", 19), + ("יח", 18), + ("יז", 17), + ("טז", 16), + ("טו", 15), + ("י", 10), + ("ט", 9), + ("ח", 8), + ("ז", 7), + ("ו", 6), + ("ה", 5), + ("ד", 4), + ("ג", 3), + ("ב", 2), + ("א", 1), + ]), + + Self::LowerSimplifiedChinese => { + NumeralSystem::Chinese(ChineseVariant::Simple, ChineseCase::Lower) + } + + Self::UpperSimplifiedChinese => { + NumeralSystem::Chinese(ChineseVariant::Simple, ChineseCase::Upper) + } + + Self::LowerTraditionalChinese => { + NumeralSystem::Chinese(ChineseVariant::Traditional, ChineseCase::Lower) + } + + Self::UpperTraditionalChinese => { + NumeralSystem::Chinese(ChineseVariant::Traditional, ChineseCase::Upper) + } + + Self::HiraganaAiueo => NumeralSystem::Bijective(&[ + 'あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ', 'さ', 'し', + 'す', 'せ', 'そ', 'た', 'ち', 'つ', 'て', 'と', 'な', 'に', 'ぬ', 'ね', + 'の', 'は', 'ひ', 'ふ', 'へ', 'ほ', 'ま', 'み', 'む', 'め', 'も', 'や', + 'ゆ', 'よ', 'ら', 'り', 'る', 'れ', 'ろ', 'わ', 'を', 'ん', + ]), + + Self::HiraganaIroha => NumeralSystem::Bijective(&[ + 'い', 'ろ', 'は', 'に', 'ほ', 'へ', 'と', 'ち', 'り', 'ぬ', 'る', 'を', + 'わ', 'か', 'よ', 'た', 'れ', 'そ', 'つ', 'ね', 'な', 'ら', 'む', 'う', + 'ゐ', 'の', 'お', 'く', 'や', 'ま', 'け', 'ふ', 'こ', 'え', 'て', 'あ', + 'さ', 'き', 'ゆ', 'め', 'み', 'し', 'ゑ', 'ひ', 'も', 'せ', 'す', + ]), + + Self::KatakanaAiueo => NumeralSystem::Bijective(&[ + 'ア', 'イ', 'ウ', 'エ', 'オ', 'カ', 'キ', 'ク', 'ケ', 'コ', 'サ', 'シ', + 'ス', 'セ', 'ソ', 'タ', 'チ', 'ツ', 'テ', 'ト', 'ナ', 'ニ', 'ヌ', 'ネ', + 'ノ', 'ハ', 'ヒ', 'フ', 'ヘ', 'ホ', 'マ', 'ミ', 'ム', 'メ', 'モ', 'ヤ', + 'ユ', 'ヨ', 'ラ', 'リ', 'ル', 'レ', 'ロ', 'ワ', 'ヲ', 'ン', + ]), + + Self::KatakanaIroha => NumeralSystem::Bijective(&[ + 'イ', 'ロ', 'ハ', 'ニ', 'ホ', 'ヘ', 'ト', 'チ', 'リ', 'ヌ', 'ル', 'ヲ', + 'ワ', 'カ', 'ヨ', 'タ', 'レ', 'ソ', 'ツ', 'ネ', 'ナ', 'ラ', 'ム', 'ウ', + 'ヰ', 'ノ', 'オ', 'ク', 'ヤ', 'マ', 'ケ', 'フ', 'コ', 'エ', 'テ', 'ア', + 'サ', 'キ', 'ユ', 'メ', 'ミ', 'シ', 'ヱ', 'ヒ', 'モ', 'セ', 'ス', + ]), + + Self::KoreanJamo => NumeralSystem::Bijective(&[ + 'ㄱ', 'ㄴ', 'ㄷ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅅ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ', 'ㅌ', + 'ㅍ', 'ㅎ', + ]), + + Self::KoreanSyllable => NumeralSystem::Bijective(&[ + '가', '나', '다', '라', '마', '바', '사', '아', '자', '차', '카', '타', + '파', '하', + ]), + + Self::EasternArabic => NumeralSystem::Positional(&[ + '٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩', + ]), + + Self::Persian => NumeralSystem::Positional(&[ + '۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹', + ]), + + Self::Devanagari => NumeralSystem::Positional(&[ + '०', '१', '२', '३', '४', '५', '६', '७', '८', '९', + ]), + + Self::Bengali => NumeralSystem::Positional(&[ + '০', '১', '২', '৩', '৪', '৫', '৬', '৭', '৮', '৯', + ]), + + Self::BengaliLetters => NumeralSystem::Bijective(&[ + 'ক', 'খ', 'গ', 'ঘ', 'ঙ', 'চ', 'ছ', 'জ', 'ঝ', 'ঞ', 'ট', 'ঠ', 'ড', 'ঢ', + 'ণ', 'ত', 'থ', 'দ', 'ধ', 'ন', 'প', 'ফ', 'ব', 'ভ', 'ম', 'য', 'র', 'ল', + 'শ', 'ষ', 'স', 'হ', + ]), + + Self::Symbols => NumeralSystem::Symbolic(&['*', '†', '‡', '§', '¶', '‖']), + } + } +} + +impl From for NumeralSystem<'static> { + fn from(value: NamedNumeralSystem) -> Self { + value.system() + } +} + +/// Represents a numeral system of one of multiple predefined kinds. +/// +/// Values of this type can be constructed manually. Alternatively, common +/// numeral systems are listed as the values of [`NamedNumeralSystem`]. +#[non_exhaustive] +#[derive(Debug, Clone, Copy)] +pub enum NumeralSystem<'a> { + /// A big-endian + /// [positional notation](https://en.wikipedia.org/wiki/Positional_notation) + /// system. + /// + /// ## Representable Numbers + /// + /// A numeral system of this kind can represent any non-negative integer. + /// + /// ## Example + /// + /// With the digits `['0', '1', '2']`, we obtain the ternary numeral system: + /// + /// | Number | Representation | + /// |--------|----------------| + /// | 0 | 0 | + /// | 1 | 1 | + /// | 2 | 2 | + /// | 3 | 10 | + /// | 4 | 12 | + /// | 5 | 12 | + /// | 6 | 20 | + Positional(&'a [char]), + + /// A big-endian + /// [bijective numeration](https://en.wikipedia.org/wiki/Bijective_numeration) + /// system. This is similar to positional notation, but without a digit for + /// zero. + /// + /// ## Representable Numbers + /// + /// A numeral system of this kind can represent any positive integer. + /// + /// ## Example + /// + /// With the digits `['A', 'B', 'C']`, we obtain a system similar to one + /// commonly used to number columns in spreadsheet software: + /// + /// | Number | Representation | + /// |--------|----------------| + /// | 1 | A | + /// | 2 | B | + /// | 3 | C | + /// | 4 | AA | + /// | 5 | AB | + /// | 6 | AC | + /// | 7 | BA | + Bijective(&'a [char]), + + /// An additive + /// [sign-value notation](https://en.wikipedia.org/wiki/Sign-value_notation) + /// system. + /// + /// The numerals must be specified by decreasing value. + /// + /// ## Representable Numbers + /// + /// A numeral system of this kind can represent any positive integer. If a + /// numeral with null value is provided, the system can represent zero as + /// well. + /// + /// ## Examples + /// + /// With the numerals `[("V", 5), ("IV", 4), ("I", 1)]`, we obtain the start + /// of the Roman numeral system: + /// + /// | Number | Representation | + /// |--------|----------------| + /// | 1 | I | + /// | 2 | II | + /// | 3 | III | + /// | 4 | IV | + /// | 5 | V | + /// | 6 | VI | + /// | 7 | VII | + /// + /// With the numerals `[("I", 1), ("Z", 0)]`, we obtain a unary system that + /// can represent zero with a non-empty string: + /// + /// | Number | Representation | + /// |--------|----------------| + /// | 0 | Z | + /// | 1 | I | + /// | 2 | II | + /// | 3 | III | + /// | 4 | IIII | + /// | 5 | IIIII | + Additive(&'a [(&'a str, u64)]), + + /// A system that uses repeating symbols. + /// + /// ## Representable Numbers + /// + /// A numeral system of this kind can represent any positive integer. + /// + /// ## Example + /// + /// With the symbols `['A', 'B', 'C']`, we obtain the following + /// representations: + /// + /// | Number | Representation | + /// |--------|----------------| + /// | 1 | A | + /// | 2 | B | + /// | 3 | C | + /// | 4 | AA | + /// | 5 | BB | + /// | 6 | CC | + /// | 7 | AAA | + Symbolic(&'a [char]), + + /// A system that uses a fixed set of symbols to represent the first + /// non-negative integers. + /// + /// ## Representable Numbers + /// + /// A numeral system of this kind can represent any non-negative integer. + /// + /// ## Example + /// + /// With the symbols `['A', 'B', 'C']`, we obtain the following + /// representations: + /// + /// | Number | Representation | + /// |--------|----------------| + /// | 0 | A | + /// | 1 | B | + /// | 2 | C | + Fixed(&'a [char]), + + /// A system that uses a fixed set of symbols to represent the first + /// positive integers. + /// + /// ## Representable Numbers + /// + /// A numeral system of this kind can represent any positive integer. + /// + /// ## Example + /// + /// With the symbols `['A', 'B', 'C']`, we obtain the following + /// representations: + /// + /// | Number | Representation | + /// |--------|----------------| + /// | 1 | A | + /// | 2 | B | + /// | 3 | C | + ZerolessFixed(&'a [char]), + + /// A Chinese numeral system. + /// + /// ## Representable Numbers + /// + /// Chinese numeral systems can represent any non-negative integer. + /// + /// ## Example + /// + /// With [`ChineseVariant::Simple`] and [`ChineseCase::Lower`], we + /// obtain the following representations: + /// + /// | Number | Representation | + /// |--------|----------------| + /// | 0 | 零 | + /// | 1 | 一 | + /// | 2 | 二 | + /// | 3 | 三 | + /// | 4 | 四 | + /// | 5 | 五 | + /// | 6 | 六 | + Chinese(ChineseVariant, ChineseCase), +} + +impl<'a> NumeralSystem<'a> { + /// Tries to represent a number in this numeral system. + /// + /// If `Ok(r)` is returned, `r` is a value of a type that implements + /// [`Display`] by printing the number as represented in this numeral + /// system. + pub const fn represent( + self, + number: u64, + ) -> Result, RepresentationError> { + match self { + Self::Positional(_) | Self::Chinese(_, _) => {} + Self::Bijective(_) | Self::Symbolic(_) => { + if number == 0 { + return Err(RepresentationError::Zero); + } + } + Self::Additive(numerals) => { + if number == 0 && !matches!(numerals.last(), Some((_, 0))) { + return Err(RepresentationError::Zero); + } + } + Self::Fixed(symbols) => { + if number as usize >= symbols.len() { + return Err(RepresentationError::TooLarge); + } + } + Self::ZerolessFixed(symbols) => { + if number == 0 { + return Err(RepresentationError::Zero); + } + if number as usize > symbols.len() { + return Err(RepresentationError::TooLarge); + } + } + } + Ok(RepresentedNumber { system: self, number }) + } +} + +/// A number, together with a numeral system in which it is representable. +/// +/// Notably, this type implements [`Display`] and is thus compatible with +/// [`format!()`]. +/// +/// Values of this type are constructed by [`NumeralSystem::represent`]. +#[derive(Debug, Clone, Copy)] +pub struct RepresentedNumber<'a> { + /// Invariant: This system must be able to represent the number. + system: NumeralSystem<'a>, + number: u64, +} + +impl<'a> Display for RepresentedNumber<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self.system { + NumeralSystem::Positional(digits) => { + let mut n = self.number; + + if n == 0 { + return write!(f, "{}", digits[0]); + } + + let radix = digits.len() as u64; + let size = n.ilog(radix) + 1; + // The place value of the most significant digit. For a number + // of size 1, the MSD's place is the ones place, hence `- 1`. + let mut msd_place = radix.pow(size - 1); + for _ in 0..size { + let msd = n / msd_place; + write!(f, "{}", digits[msd as usize])?; + n -= msd * msd_place; + msd_place /= radix; + } + Ok(()) + } + + NumeralSystem::Bijective(digits) => { + let mut n = self.number; + + assert_ne!(n, 0); + + let radix = digits.len() as u64; + // Number of digits when representing `n` in this system. + // From https://en.wikipedia.org/wiki/Bijective_numeration#Properties_of_bijective_base-k_numerals. + let size = ((n + 1) * (radix - 1)).ilog(radix); + // Remove from `n` the number consisting of `size - 1` ones in + // base-`radix`, and the print the result using the symbols as + // a positional numeral system. + n -= (radix.pow(size) - 1) / (radix - 1); + // The place value of the most significant digit. For a number + // of size 1, the MSD's place is the ones place, hence `- 1`. + let mut msd_place = radix.pow(size - 1); + for _ in 0..size { + let msd = n / msd_place; + write!(f, "{}", digits[msd as usize])?; + n -= msd * msd_place; + msd_place /= radix; + } + Ok(()) + } + + NumeralSystem::Additive(numerals) => { + let mut n = self.number; + + if n == 0 { + if let Some(&(numeral, 0)) = numerals.last() { + return write!(f, "{}", numeral); + } + unreachable!() + } + + // Greedily add any symbol that fits. + for (numeral, weight) in numerals { + if *weight == 0 || *weight > n { + continue; + } + let reps = n / weight; + for _ in 0..reps { + write!(f, "{}", numeral)? + } + + n -= weight * reps; + } + Ok(()) + } + NumeralSystem::Symbolic(symbols) => { + let n = self.number; + assert_ne!(n, 0); + let symbol_count = symbols.len() as u64; + for _ in 0..n.div_ceil(symbol_count) { + write!(f, "{}", symbols[((n - 1) % symbol_count) as usize])? + } + Ok(()) + } + + NumeralSystem::Fixed(symbols) => { + write!(f, "{}", symbols[self.number as usize]) + } + + NumeralSystem::ZerolessFixed(symbols) => { + write!(f, "{}", symbols[(self.number - 1) as usize]) + } + + NumeralSystem::Chinese(variant, case) => write!( + f, + "{}", + from_u64_to_chinese_ten_thousand(variant, case, self.number), + ), + } + } +} + +/// A reason why a number cannot be represented in a numeral system. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub enum RepresentationError { + /// Zero cannot be represented in the numeral system. + Zero, + /// The number is too large for the numeral system. + TooLarge, +} + +#[cfg(test)] +mod tests { + use super::{NamedNumeralSystem, NumeralSystem}; + + /// Makes sure shorthands correspond to the way the number one is + /// represented in the corresponding system. + #[test] + fn shorthands_are_one() { + for named in NamedNumeralSystem::iter() { + if let Some(shorthand) = named.shorthand() { + assert_eq!( + named + .system() + .represent(1) + .unwrap_or_else(|_| panic!( + "one should be representable with `{}`", + named.name(), + )) + .to_string(), + shorthand, + ) + } + } + } + + /// Makes sure fixed systems are implemented properly. + #[test] + fn test_fixed_systems() { + let symbols = ('a'..='z').collect::>(); + for n in 0..symbols.len() { + for i in 0..n { + assert_eq!( + NumeralSystem::Fixed(&symbols[0..n]) + .represent(i as u64) + .unwrap() + .to_string(), + symbols[i].to_string(), + ); + assert_eq!( + NumeralSystem::ZerolessFixed(&symbols[0..n]) + .represent(i as u64 + 1) + .unwrap() + .to_string(), + symbols[i].to_string(), + ) + } + } + } + + /// Makes sure [`NamedNumeralSystem::Arabic`] represents numbers properly. + /// This also tests [`NumeralSystem::Positional`]. + #[test] + fn test_arabic_numerals() { + for n in 0..=9999 { + assert_eq!( + NamedNumeralSystem::Arabic.system().represent(n).unwrap().to_string(), + n.to_string(), + ) + } + } + + /// Makes sure [`NamedNumeralSystem::LowerLatin`] and + /// [`NamedNumeralSystem::UpperLatin`] represent numbers properly. This also + /// tests [`NumeralSystem::Bijective`]. + #[test] + fn test_latin() { + let mut n = 1; + for c1 in 'a'..='z' { + assert_eq!( + NamedNumeralSystem::LowerLatin + .system() + .represent(n) + .unwrap() + .to_string(), + format!("{c1}"), + ); + assert_eq!( + NamedNumeralSystem::UpperLatin + .system() + .represent(n) + .unwrap() + .to_string(), + format!("{c1}").to_uppercase(), + ); + n += 1 + } + for c2 in 'a'..='z' { + for c1 in 'a'..='z' { + assert_eq!( + NamedNumeralSystem::LowerLatin + .system() + .represent(n) + .unwrap() + .to_string(), + format!("{c2}{c1}"), + ); + assert_eq!( + NamedNumeralSystem::UpperLatin + .system() + .represent(n) + .unwrap() + .to_string(), + format!("{c2}{c1}").to_uppercase(), + ); + n += 1 + } + } + for c3 in 'a'..='z' { + for c2 in 'a'..='z' { + for c1 in 'a'..='z' { + assert_eq!( + NamedNumeralSystem::LowerLatin + .system() + .represent(n) + .unwrap() + .to_string(), + format!("{c3}{c2}{c1}"), + ); + assert_eq!( + NamedNumeralSystem::UpperLatin + .system() + .represent(n) + .unwrap() + .to_string(), + format!("{c3}{c2}{c1}").to_uppercase(), + ); + n += 1 + } + } + } + } + + /// Makes sure Roman numerals work properly. This also tests + /// [`NumeralSystem::Additive`]. + #[test] + fn test_roman() { + for (n, expect) in [ + "n", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix", "x", "xi", + "xii", "xiii", "xiv", "xv", "xvi", "xvii", "xviii", "xix", "xx", "xxi", + "xxii", "xxiii", "xxiv", "xxv", "xxvi", "xxvii", "xxviii", "xxix", "xxx", + "xxxi", "xxxii", "xxxiii", "xxxiv", "xxxv", "xxxvi", "xxxvii", "xxxviii", + "xxxix", "xl", "xli", "xlii", "xliii", "xliv", "xlv", "xlvi", + ] + .iter() + .enumerate() + { + assert_eq!( + &NamedNumeralSystem::LowerRoman + .system() + .represent(n as u64) + .unwrap() + .to_string(), + expect, + ); + assert_eq!( + NamedNumeralSystem::UpperRoman + .system() + .represent(n as u64) + .unwrap() + .to_string(), + expect.to_uppercase(), + ); + } + } + + /// Makes sure [`NumeralSystem::Symbolic`] represents numbers properly. + #[test] + fn test_symbolic() { + let expected = ["a", "b", "aa", "bb", "aaa", "bbb", "aaaa", "bbbb"]; + for (i, r) in expected.iter().enumerate() { + assert_eq!( + &NumeralSystem::Symbolic(&['a', 'b']) + .represent(i as u64 + 1) + .unwrap() + .to_string(), + r, + ) + } + } + + // #[test] + // fn foo() { + // let mut values = String::new(); + // for i in 0..12 { + // if i != 0 { + // values.push_str(", ") + // } + // // values.push_str(""); + // values.push_str( + // &NamedNumeralSystem::BengaliLetters + // .system() + // .represent(i + 1) + // .unwrap() + // .to_string(), + // ); + // // values.push_str(""); + // } + // panic!("{values}") + // } +}