From 0627aea9de0d80753300c169e9e86aec893b37d1 Mon Sep 17 00:00:00 2001 From: Malo <57839069+MDLC01@users.noreply.github.com> Date: Fri, 8 Aug 2025 00:23:19 +0200 Subject: [PATCH 01/12] Add numbering formats --- Cargo.lock | 119 ++++++++- Cargo.toml | 7 +- src/lib.rs | 3 + src/numbering_formats.rs | 565 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 692 insertions(+), 2 deletions(-) create mode 100644 src/numbering_formats.rs diff --git a/Cargo.lock b/Cargo.lock index 0efb2fd..f07841a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,7 +1,124 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "chinese-number" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49fccaef6346f6d6a741908d3b79fe97c2debe2fbb5eb3a7d00ff5981b52bb6c" +dependencies = [ + "chinese-variant", + "enum-ordinalize", + "num-bigint", + "num-traits", +] + +[[package]] +name = "chinese-variant" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7588475145507237ded760e52bf2f1085495245502033756d28ea72ade0e498b" [[package]] name = "codex" version = "0.1.1" +dependencies = [ + "chinese-number", + "ecow", +] + +[[package]] +name = "ecow" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78e4f79b296fbaab6ce2e22d52cb4c7f010fe0ebe7a32e34fa25885fd797bd02" + +[[package]] +name = "enum-ordinalize" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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 = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" diff --git a/Cargo.toml b/Cargo.toml index c0d0258..f8fc131 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,5 +11,10 @@ categories = ["encoding", "text-processing"] keywords = ["unicode", "symbols"] [features] -default = ["styling"] +default = ["styling", "numbering-formats"] +numbering-formats = ["chinese-number", "ecow"] styling = [] + +[dependencies] +chinese-number = { version = "0.7.7", default-features = false, features = ["number-to-chinese"], optional = true } +ecow = { version = "0.2.6", optional = true } diff --git a/src/lib.rs b/src/lib.rs index 0455ae7..b1f043d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,9 @@ pub use self::shared::ModifierSet; mod shared; +#[cfg(feature = "numbering-formats")] +pub mod numbering_formats; + #[cfg(feature = "styling")] pub mod styling; diff --git a/src/numbering_formats.rs b/src/numbering_formats.rs new file mode 100644 index 0000000..79e1a7c --- /dev/null +++ b/src/numbering_formats.rs @@ -0,0 +1,565 @@ +//! Various ways of displaying integers. + +use chinese_number::{from_u64_to_chinese_ten_thousand, ChineseCase, ChineseVariant}; +use ecow::{eco_format, EcoString}; + +macro_rules! declare_variants { + { + $( #[$attr:meta] )* + $vis:vis enum $Variants:ident { + $( + $( #[$variant_attr:meta] )* + $variant:ident = $name:literal, + )* + } + } => { + $( #[$attr] )* + $vis enum $Variants { + $( + $( #[$variant_attr] )* + $variant, + )* + } + + impl $Variants { + pub fn from_name(s: &str) -> Option { + match s { + $( $name => Some(Self::$variant), )* + _ => None, + } + } + + pub fn name(self) -> &'static str { + match self { + $( Self::$variant => $name, )* + } + } + } + }; +} + +declare_variants! { + /// Formats for displaying numbers. + #[non_exhaustive] + #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] + pub enum NumberFormat { + /// Arabic numerals (1, 2, 3, etc.). + Arabic = "arabic", + /// Lowercase Latin letters (a, b, c, etc.). Items beyond z use base-26. + LowerLatin = "latin", + /// Uppercase Latin letters (A, B, C, etc.). Items beyond Z use base-26. + UpperLatin = "Latin", + /// Lowercase Roman numerals (i, ii, iii, etc.). + LowerRoman = "roman", + /// Uppercase Roman numerals (I, II, III, etc.). + UpperRoman = "Roman", + /// Lowercase Greek letters (α, β, γ, etc.). + LowerGreek = "greek", + /// Uppercase Greek letters (Α, Β, Γ, etc.). + UpperGreek = "Greek", + /// Paragraph/note-like symbols: *, †, ‡, §, ¶, and ‖. Further items use + /// repeated symbols. + Symbol = "symbols", + /// Hebrew numerals, including Geresh/Gershayim. + Hebrew = "hebrew", + /// Simplified Chinese standard numerals. This corresponds to the + /// `ChineseCase::Lower` variant. + LowerSimplifiedChinese = "chinese.simplified", + /// Simplified Chinese "banknote" numerals. This corresponds to the + /// `ChineseCase::Upper` variant. + UpperSimplifiedChinese = "Chinese.simplified", + /// Traditional Chinese standard numerals. This corresponds to the + /// `ChineseCase::Lower` variant. + LowerTraditionalChinese = "chinese.traditional", + /// Traditional Chinese "banknote" numerals. This corresponds to the + /// `ChineseCase::Upper` variant. + UpperTraditionalChinese = "Chinese.traditional", + /// Hiragana in the gojūon order. Includes n but excludes wi and we. + HiraganaAiueo = "hiragana.aiueo", + /// Hiragana in the iroha order. Includes wi and we but excludes n. + HiraganaIroha = "hiragana.iroha", + /// Katakana in the gojūon order. Includes n but excludes wi and we. + KatakanaAiueo = "katakana.aiueo", + /// Katakana in the iroha order. Includes wi and we but excludes n. + KatakanaIroha = "katakana.oroha", + /// Korean jamo (ㄱ, ㄴ, ㄷ, etc.). + KoreanJamo = "korean.jamo", + /// Korean syllables (가, 나, 다, etc.). + KoreanSyllable = "korean.syllable", + /// Eastern Arabic numerals, used in some Arabic-speaking countries. + EasternArabic = "arabic.eastern", + /// The variant of Eastern Arabic numerals used in Persian and Urdu. + EasternArabicPersian = "arabic.persan", + /// Devanagari numerals. + DevanagariNumber = "devanagari", + /// Bengali numerals. + BengaliNumber = "bengali.number", + /// Bengali letters (ক, খ, গ, ...কক, কখ etc.). + BengaliLetter = "bengali.letter", + /// Circled numbers (①, ②, ③, etc.), up to 50. + CircledNumber = "circled", + /// Double-circled numbers (⓵, ⓶, ⓷, etc.), up to 10. + DoubleCircledNumber = "circled.double", + } +} + +impl NumberFormat { + /// Apply the numbering to the given number. + pub fn apply(self, n: u64) -> EcoString { + match self { + Self::Arabic => { + numeric(&['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], n) + } + Self::LowerRoman => 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), + ], + n, + ), + Self::UpperRoman => 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), + ], + n, + ), + Self::LowerGreek => 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), + ], + n, + ), + Self::UpperGreek => 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), + ], + n, + ), + Self::Hebrew => 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), + ("-", 0), + ], + n, + ), + Self::LowerLatin => alphabetic( + &[ + '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', + ], + n, + ), + Self::UpperLatin => alphabetic( + &[ + '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', + ], + n, + ), + Self::HiraganaAiueo => alphabetic( + &[ + 'あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ', 'さ', + 'し', 'す', 'せ', 'そ', 'た', 'ち', 'つ', 'て', 'と', 'な', 'に', + 'ぬ', 'ね', 'の', 'は', 'ひ', 'ふ', 'へ', 'ほ', 'ま', 'み', 'む', + 'め', 'も', 'や', 'ゆ', 'よ', 'ら', 'り', 'る', 'れ', 'ろ', 'わ', + 'を', 'ん', + ], + n, + ), + Self::HiraganaIroha => alphabetic( + &[ + 'い', 'ろ', 'は', 'に', 'ほ', 'へ', 'と', 'ち', 'り', 'ぬ', 'る', + 'を', 'わ', 'か', 'よ', 'た', 'れ', 'そ', 'つ', 'ね', 'な', 'ら', + 'む', 'う', 'ゐ', 'の', 'お', 'く', 'や', 'ま', 'け', 'ふ', 'こ', + 'え', 'て', 'あ', 'さ', 'き', 'ゆ', 'め', 'み', 'し', 'ゑ', 'ひ', + 'も', 'せ', 'す', + ], + n, + ), + Self::KatakanaAiueo => alphabetic( + &[ + 'ア', 'イ', 'ウ', 'エ', 'オ', 'カ', 'キ', 'ク', 'ケ', 'コ', 'サ', + 'シ', 'ス', 'セ', 'ソ', 'タ', 'チ', 'ツ', 'テ', 'ト', 'ナ', 'ニ', + 'ヌ', 'ネ', 'ノ', 'ハ', 'ヒ', 'フ', 'ヘ', 'ホ', 'マ', 'ミ', 'ム', + 'メ', 'モ', 'ヤ', 'ユ', 'ヨ', 'ラ', 'リ', 'ル', 'レ', 'ロ', 'ワ', + 'ヲ', 'ン', + ], + n, + ), + Self::KatakanaIroha => alphabetic( + &[ + 'イ', 'ロ', 'ハ', 'ニ', 'ホ', 'ヘ', 'ト', 'チ', 'リ', 'ヌ', 'ル', + 'ヲ', 'ワ', 'カ', 'ヨ', 'タ', 'レ', 'ソ', 'ツ', 'ネ', 'ナ', 'ラ', + 'ム', 'ウ', 'ヰ', 'ノ', 'オ', 'ク', 'ヤ', 'マ', 'ケ', 'フ', 'コ', + 'エ', 'テ', 'ア', 'サ', 'キ', 'ユ', 'メ', 'ミ', 'シ', 'ヱ', 'ヒ', + 'モ', 'セ', 'ス', + ], + n, + ), + Self::KoreanJamo => alphabetic( + &[ + 'ㄱ', 'ㄴ', 'ㄷ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅅ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ', + 'ㅌ', 'ㅍ', 'ㅎ', + ], + n, + ), + Self::KoreanSyllable => alphabetic( + &[ + '가', '나', '다', '라', '마', '바', '사', '아', '자', '차', '카', + '타', '파', '하', + ], + n, + ), + Self::BengaliLetter => alphabetic( + &[ + 'ক', 'খ', 'গ', 'ঘ', 'ঙ', 'চ', 'ছ', 'জ', 'ঝ', 'ঞ', 'ট', 'ঠ', 'ড', 'ঢ', + 'ণ', 'ত', 'থ', 'দ', 'ধ', 'ন', 'প', 'ফ', 'ব', 'ভ', 'ম', 'য', 'র', 'ল', + 'শ', 'ষ', 'স', 'হ', + ], + n, + ), + Self::CircledNumber => fixed( + &[ + '⓪', '①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩', '⑪', '⑫', '⑬', + '⑭', '⑮', '⑯', '⑰', '⑱', '⑲', '⑳', '㉑', '㉒', '㉓', '㉔', '㉕', + '㉖', '㉗', '㉘', '㉙', '㉚', '㉛', '㉜', '㉝', '㉞', '㉟', '㊱', + '㊲', '㊳', '㊴', '㊵', '㊶', '㊷', '㊸', '㊹', '㊺', '㊻', '㊼', + '㊽', '㊾', '㊿', + ], + n, + ), + Self::DoubleCircledNumber => { + fixed(&['0', '⓵', '⓶', '⓷', '⓸', '⓹', '⓺', '⓻', '⓼', '⓽', '⓾'], n) + } + + Self::LowerSimplifiedChinese => from_u64_to_chinese_ten_thousand( + ChineseVariant::Simple, + ChineseCase::Lower, + n, + ) + .into(), + Self::UpperSimplifiedChinese => from_u64_to_chinese_ten_thousand( + ChineseVariant::Simple, + ChineseCase::Upper, + n, + ) + .into(), + Self::LowerTraditionalChinese => from_u64_to_chinese_ten_thousand( + ChineseVariant::Traditional, + ChineseCase::Lower, + n, + ) + .into(), + Self::UpperTraditionalChinese => from_u64_to_chinese_ten_thousand( + ChineseVariant::Traditional, + ChineseCase::Upper, + n, + ) + .into(), + + Self::EasternArabic => { + numeric(&['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'], n) + } + Self::EasternArabicPersian => { + numeric(&['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'], n) + } + Self::DevanagariNumber => { + numeric(&['०', '१', '२', '३', '४', '५', '६', '७', '८', '९'], n) + } + Self::BengaliNumber => { + numeric(&['০', '১', '২', '৩', '৪', '৫', '৬', '৭', '৮', '৯'], n) + } + Self::Symbol => symbolic(&['*', '†', '‡', '§', '¶', '‖'], n), + } + } +} + +/// Stringifies a number using symbols representing values. +/// +/// The value of a stringified number is recovered by summing over the values of +/// the symbols present. +/// +/// Consider the situation where `[("I", 1), ("IV", 4), ("V", 5)]` are the +/// provided symbols: +/// +/// ```text +/// 1 => 'I' +/// 2 => 'II' +/// 3 => 'III' +/// 4 => 'IV' +/// 5 => 'V' +/// 6 => 'VI' +/// 7 => 'VII' +/// 8 => 'VIII' +/// ``` +/// +/// This is the start of the familiar Roman numeral system. +fn additive(symbols: &[(&str, u64)], mut n: u64) -> EcoString { + if n == 0 { + if let Some(&(symbol, 0)) = symbols.last() { + return symbol.into(); + } + return '0'.into(); + } + + let mut s = EcoString::new(); + for (symbol, weight) in symbols { + if *weight == 0 || *weight > n { + continue; + } + let reps = n / weight; + for _ in 0..reps { + s.push_str(symbol); + } + + n -= weight * reps; + if n == 0 { + return s; + } + } + s +} + +/// Stringifies a number using a base-_b_ (where _b_ is the number of provided +/// symbols) system without a zero symbol. +/// +/// Consider the situation where `['A', 'B', 'C']` are the provided symbols: +/// +/// ```text +/// 1 => "A" +/// 2 => "B" +/// 3 => "C" +/// 4 => "AA" +/// 5 => "AB" +/// 6 => "AC" +/// 7 => "BA" +/// ... +/// ``` +/// +/// A similar system is commonly used in spreadsheet software. +fn alphabetic(symbols: &[char], mut n: u64) -> EcoString { + let n_digits = symbols.len() as u64; + if n == 0 { + return '-'.into(); + } + let mut s = EcoString::new(); + while n != 0 { + n -= 1; + s.push(symbols[(n % n_digits) as usize]); + n /= n_digits; + } + s.chars().rev().collect() +} + +/// Stringifies a number using the symbols provided, defaulting to the arabic +/// representation when the number is greater than the number of symbols. +/// +/// Consider the situation where `['0', 'A', 'B', 'C']` are the provided +/// symbols: +/// +/// ```text +/// 0 => "0" +/// 1 => "A" +/// 2 => "B" +/// 3 => "C" +/// 4 => "4" +/// ... +/// ``` +fn fixed(symbols: &[char], n: u64) -> EcoString { + let n_digits = symbols.len() as u64; + if n < n_digits { + return symbols[n as usize].into(); + } + eco_format!("{n}") +} + +/// Stringifies a number using a base-_b_ (where _b_ is the number of provided +/// symbols) system with a zero symbol. +/// +/// Consider the situation where `['0', '1', '2']` are the provided symbols: +/// +/// ```text +/// 0 => "0" +/// 1 => "1" +/// 2 => "2" +/// 3 => "10" +/// 4 => "11" +/// 5 => "12" +/// 6 => "20" +/// ... +/// ``` +/// +/// This is the familiar trinary counting system. +fn numeric(symbols: &[char], mut n: u64) -> EcoString { + let n_digits = symbols.len() as u64; + if n == 0 { + return symbols[0].into(); + } + let mut s = EcoString::new(); + while n != 0 { + s.push(symbols[(n % n_digits) as usize]); + n /= n_digits; + } + s.chars().rev().collect() +} + +/// Stringifies a number using repeating symbols. +/// +/// Consider the situation where `['A', 'B', 'C']` are the provided symbols: +/// +/// ```text +/// 0 => "-" +/// 1 => "A" +/// 2 => "B" +/// 3 => "C" +/// 4 => "AA" +/// 5 => "BB" +/// 6 => "CC" +/// 7 => "AAA" +/// ... +/// ``` +fn symbolic(symbols: &[char], n: u64) -> EcoString { + let n_digits = symbols.len() as u64; + if n == 0 { + return '-'.into(); + } + EcoString::from(symbols[((n - 1) % n_digits) as usize]) + .repeat(n.div_ceil(n_digits) as usize) +} From 41d4aece82f20af0cbb0fd1f0f4ce8a3280fadbf Mon Sep 17 00:00:00 2001 From: Malo <57839069+MDLC01@users.noreply.github.com> Date: Fri, 8 Aug 2025 00:41:29 +0200 Subject: [PATCH 02/12] Add numeral systems --- Cargo.toml | 4 ++-- src/lib.rs | 4 ++-- src/{numbering_formats.rs => numeral_systems.rs} | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) rename src/{numbering_formats.rs => numeral_systems.rs} (99%) diff --git a/Cargo.toml b/Cargo.toml index f8fc131..c094b4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,8 @@ categories = ["encoding", "text-processing"] keywords = ["unicode", "symbols"] [features] -default = ["styling", "numbering-formats"] -numbering-formats = ["chinese-number", "ecow"] +default = ["styling", "numbering-systems"] +numbering-systems = ["chinese-number", "ecow"] styling = [] [dependencies] diff --git a/src/lib.rs b/src/lib.rs index b1f043d..8b3f8c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,8 +11,8 @@ pub use self::shared::ModifierSet; mod shared; -#[cfg(feature = "numbering-formats")] -pub mod numbering_formats; +#[cfg(feature = "numbering-systems")] +pub mod numeral_systems; #[cfg(feature = "styling")] pub mod styling; diff --git a/src/numbering_formats.rs b/src/numeral_systems.rs similarity index 99% rename from src/numbering_formats.rs rename to src/numeral_systems.rs index 79e1a7c..2ddcfbb 100644 --- a/src/numbering_formats.rs +++ b/src/numeral_systems.rs @@ -39,10 +39,10 @@ macro_rules! declare_variants { } declare_variants! { - /// Formats for displaying numbers. + /// Various numeral systems used worldwide. #[non_exhaustive] #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] - pub enum NumberFormat { + pub enum NumeralSystem { /// Arabic numerals (1, 2, 3, etc.). Arabic = "arabic", /// Lowercase Latin letters (a, b, c, etc.). Items beyond z use base-26. @@ -103,8 +103,8 @@ declare_variants! { } } -impl NumberFormat { - /// Apply the numbering to the given number. +impl NumeralSystem { + /// Represents a non-negative integer with this numeral system. pub fn apply(self, n: u64) -> EcoString { match self { Self::Arabic => { From 912f910028b006ffa1abbe6506545aad465e346c Mon Sep 17 00:00:00 2001 From: Malo <57839069+MDLC01@users.noreply.github.com> Date: Mon, 11 Aug 2025 13:13:06 +0200 Subject: [PATCH 03/12] Rename `numbering-systems` feature to `numeral-systems` --- Cargo.toml | 4 ++-- src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c094b4a..62e98b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,8 @@ categories = ["encoding", "text-processing"] keywords = ["unicode", "symbols"] [features] -default = ["styling", "numbering-systems"] -numbering-systems = ["chinese-number", "ecow"] +default = ["styling", "numeral-systems"] +numeral-systems = ["chinese-number", "ecow"] styling = [] [dependencies] diff --git a/src/lib.rs b/src/lib.rs index 8b3f8c1..130c1ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,7 +11,7 @@ pub use self::shared::ModifierSet; mod shared; -#[cfg(feature = "numbering-systems")] +#[cfg(feature = "numeral-systems")] pub mod numeral_systems; #[cfg(feature = "styling")] From 678945db9dd2fba6a4763b19dab4b27b7bf50bd8 Mon Sep 17 00:00:00 2001 From: Malo <57839069+MDLC01@users.noreply.github.com> Date: Mon, 11 Aug 2025 13:19:04 +0200 Subject: [PATCH 04/12] Fix typo --- src/numeral_systems.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/numeral_systems.rs b/src/numeral_systems.rs index 2ddcfbb..b99ba05 100644 --- a/src/numeral_systems.rs +++ b/src/numeral_systems.rs @@ -89,7 +89,7 @@ declare_variants! { /// Eastern Arabic numerals, used in some Arabic-speaking countries. EasternArabic = "arabic.eastern", /// The variant of Eastern Arabic numerals used in Persian and Urdu. - EasternArabicPersian = "arabic.persan", + EasternArabicPersian = "arabic.persian", /// Devanagari numerals. DevanagariNumber = "devanagari", /// Bengali numerals. From f4a50f897bd1ab7c3ab4172af9befb7c8d9a5a76 Mon Sep 17 00:00:00 2001 From: Malo <57839069+MDLC01@users.noreply.github.com> Date: Mon, 11 Aug 2025 13:20:29 +0200 Subject: [PATCH 05/12] Update numeral system descriptions --- src/numeral_systems.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/numeral_systems.rs b/src/numeral_systems.rs index b99ba05..5e9703b 100644 --- a/src/numeral_systems.rs +++ b/src/numeral_systems.rs @@ -43,19 +43,19 @@ declare_variants! { #[non_exhaustive] #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub enum NumeralSystem { - /// Arabic numerals (1, 2, 3, etc.). + /// Base-ten Arabic numerals: 0, 1, 2, 3, ... Arabic = "arabic", - /// Lowercase Latin letters (a, b, c, etc.). Items beyond z use base-26. + /// Lowercase Latin letters: a, b, c, ..., y, z, aa, ab, ... LowerLatin = "latin", - /// Uppercase Latin letters (A, B, C, etc.). Items beyond Z use base-26. + /// Uppercase Latin letters: A, B, C, ..., Y, Z, AA, AB, ... UpperLatin = "Latin", - /// Lowercase Roman numerals (i, ii, iii, etc.). + /// Lowercase Roman numerals: i, ii, iii, ... LowerRoman = "roman", - /// Uppercase Roman numerals (I, II, III, etc.). + /// Uppercase Roman numerals: I, II, III, ... UpperRoman = "Roman", - /// Lowercase Greek letters (α, β, γ, etc.). + /// Lowercase Greek letters: α, β, γ, ... LowerGreek = "greek", - /// Uppercase Greek letters (Α, Β, Γ, etc.). + /// Uppercase Greek letters: Α, Β, Γ, ... UpperGreek = "Greek", /// Paragraph/note-like symbols: *, †, ‡, §, ¶, and ‖. Further items use /// repeated symbols. @@ -82,9 +82,9 @@ declare_variants! { KatakanaAiueo = "katakana.aiueo", /// Katakana in the iroha order. Includes wi and we but excludes n. KatakanaIroha = "katakana.oroha", - /// Korean jamo (ㄱ, ㄴ, ㄷ, etc.). + /// Korean jamo: ㄱ, ㄴ, ㄷ, ... KoreanJamo = "korean.jamo", - /// Korean syllables (가, 나, 다, etc.). + /// Korean syllables: 가, 나, 다, ... KoreanSyllable = "korean.syllable", /// Eastern Arabic numerals, used in some Arabic-speaking countries. EasternArabic = "arabic.eastern", @@ -94,11 +94,11 @@ declare_variants! { DevanagariNumber = "devanagari", /// Bengali numerals. BengaliNumber = "bengali.number", - /// Bengali letters (ক, খ, গ, ...কক, কখ etc.). + /// Bengali letters: ক, খ, গ, ..., কক, কখ, ... BengaliLetter = "bengali.letter", - /// Circled numbers (①, ②, ③, etc.), up to 50. + /// Circled numbers up to fifty: ①, ②, ③, ... CircledNumber = "circled", - /// Double-circled numbers (⓵, ⓶, ⓷, etc.), up to 10. + /// Double-circled numbers up to ten: ⓵, ⓶, ⓷, ... DoubleCircledNumber = "circled.double", } } From 7d702032345c2cad693e39498d66e457618dd45c Mon Sep 17 00:00:00 2001 From: Malo <57839069+MDLC01@users.noreply.github.com> Date: Mon, 11 Aug 2025 13:27:16 +0200 Subject: [PATCH 06/12] Update format function descriptions --- src/numeral_systems.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/numeral_systems.rs b/src/numeral_systems.rs index 5e9703b..1b2d07c 100644 --- a/src/numeral_systems.rs +++ b/src/numeral_systems.rs @@ -411,12 +411,15 @@ impl NumeralSystem { } } -/// Stringifies a number using symbols representing values. +/// Formats a number using a +/// [sign-value notation](https://en.wikipedia.org/wiki/Sign-value_notation). +/// +/// The symbols must be specified by decreasing values. /// /// The value of a stringified number is recovered by summing over the values of /// the symbols present. /// -/// Consider the situation where `[("I", 1), ("IV", 4), ("V", 5)]` are the +/// Consider the situation where `[("V", 5), ("IV", 4), ("I", 1)]` are the /// provided symbols: /// /// ```text @@ -457,8 +460,10 @@ fn additive(symbols: &[(&str, u64)], mut n: u64) -> EcoString { s } -/// Stringifies a number using a base-_b_ (where _b_ is the number of provided -/// symbols) system without a zero symbol. +/// Formats a number using a big-endian +/// [bijective base-_b_](https://en.wikipedia.org/wiki/Bijective_numeration) +/// system (where _b_ is the number of provided symbols). This is similar to +/// regular base-_b_ systems, but without a symbol for zero. /// /// Consider the situation where `['A', 'B', 'C']` are the provided symbols: /// @@ -488,7 +493,7 @@ fn alphabetic(symbols: &[char], mut n: u64) -> EcoString { s.chars().rev().collect() } -/// Stringifies a number using the symbols provided, defaulting to the arabic +/// Formats a number using the symbols provided, defaulting to the arabic /// representation when the number is greater than the number of symbols. /// /// Consider the situation where `['0', 'A', 'B', 'C']` are the provided @@ -510,8 +515,8 @@ fn fixed(symbols: &[char], n: u64) -> EcoString { eco_format!("{n}") } -/// Stringifies a number using a base-_b_ (where _b_ is the number of provided -/// symbols) system with a zero symbol. +/// Formats a number using a big-endian +/// [positional notation](https://en.wikipedia.org/wiki/Positional_notation). /// /// Consider the situation where `['0', '1', '2']` are the provided symbols: /// @@ -526,7 +531,7 @@ fn fixed(symbols: &[char], n: u64) -> EcoString { /// ... /// ``` /// -/// This is the familiar trinary counting system. +/// This is the familiar ternary numeral system. fn numeric(symbols: &[char], mut n: u64) -> EcoString { let n_digits = symbols.len() as u64; if n == 0 { @@ -540,7 +545,7 @@ fn numeric(symbols: &[char], mut n: u64) -> EcoString { s.chars().rev().collect() } -/// Stringifies a number using repeating symbols. +/// Formats a number using repeating symbols. /// /// Consider the situation where `['A', 'B', 'C']` are the provided symbols: /// From 05f39a9fa4709a1a75c4290552a6f6827e140386 Mon Sep 17 00:00:00 2001 From: Malo <57839069+MDLC01@users.noreply.github.com> Date: Mon, 11 Aug 2025 14:25:39 +0200 Subject: [PATCH 07/12] Use `Display` --- Cargo.lock | 7 - Cargo.toml | 3 +- src/numeral_systems.rs | 281 +++++++++++++++++++++++++---------------- 3 files changed, 176 insertions(+), 115 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f07841a..92f5e75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,15 +31,8 @@ name = "codex" version = "0.1.1" dependencies = [ "chinese-number", - "ecow", ] -[[package]] -name = "ecow" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78e4f79b296fbaab6ce2e22d52cb4c7f010fe0ebe7a32e34fa25885fd797bd02" - [[package]] name = "enum-ordinalize" version = "4.3.0" diff --git a/Cargo.toml b/Cargo.toml index 62e98b5..89137fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,9 +12,8 @@ keywords = ["unicode", "symbols"] [features] default = ["styling", "numeral-systems"] -numeral-systems = ["chinese-number", "ecow"] +numeral-systems = ["chinese-number"] styling = [] [dependencies] chinese-number = { version = "0.7.7", default-features = false, features = ["number-to-chinese"], optional = true } -ecow = { version = "0.2.6", optional = true } diff --git a/src/numeral_systems.rs b/src/numeral_systems.rs index 1b2d07c..b17c547 100644 --- a/src/numeral_systems.rs +++ b/src/numeral_systems.rs @@ -1,7 +1,7 @@ //! Various ways of displaying integers. use chinese_number::{from_u64_to_chinese_ten_thousand, ChineseCase, ChineseVariant}; -use ecow::{eco_format, EcoString}; +use std::fmt::{Display, Formatter}; macro_rules! declare_variants { { @@ -104,13 +104,35 @@ declare_variants! { } impl NumeralSystem { - /// Represents a non-negative integer with this numeral system. - pub fn apply(self, n: u64) -> EcoString { - match self { - Self::Arabic => { - numeric(&['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], n) - } - Self::LowerRoman => additive( + /// Formats a number using this numeral system. + /// + /// The returned value implements [`Display`], meaning it can be used in + /// [`format!()`]. + pub fn apply(self, n: u64) -> FormattedNumber { + FormattedNumber { system: self, number: n } + } +} + +/// A number, together with a numeral system to display it with. +/// +/// Notably, this type implements [`Display`] and is thus compatible with +/// [`format!()`]. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub struct FormattedNumber { + system: NumeralSystem, + number: u64, +} + +impl Display for FormattedNumber { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self.system { + NumeralSystem::Arabic => positional( + f, + &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], + self.number, + ), + NumeralSystem::LowerRoman => additive( + f, &[ ("m̅", 1000000), ("d̅", 500000), @@ -134,9 +156,10 @@ impl NumeralSystem { ("i", 1), ("n", 0), ], - n, + self.number, ), - Self::UpperRoman => additive( + NumeralSystem::UpperRoman => additive( + f, &[ ("M̅", 1000000), ("D̅", 500000), @@ -160,9 +183,10 @@ impl NumeralSystem { ("I", 1), ("N", 0), ], - n, + self.number, ), - Self::LowerGreek => additive( + NumeralSystem::LowerGreek => additive( + f, &[ ("͵θ", 9000), ("͵η", 8000), @@ -202,9 +226,10 @@ impl NumeralSystem { ("α", 1), ("𐆊", 0), ], - n, + self.number, ), - Self::UpperGreek => additive( + NumeralSystem::UpperGreek => additive( + f, &[ ("͵Θ", 9000), ("͵Η", 8000), @@ -244,9 +269,10 @@ impl NumeralSystem { ("Α", 1), ("𐆊", 0), ], - n, + self.number, ), - Self::Hebrew => additive( + NumeralSystem::Hebrew => additive( + f, &[ ("ת", 400), ("ש", 300), @@ -277,23 +303,26 @@ impl NumeralSystem { ("א", 1), ("-", 0), ], - n, + self.number, ), - Self::LowerLatin => alphabetic( + NumeralSystem::LowerLatin => bijective( + f, &[ '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', ], - n, + self.number, ), - Self::UpperLatin => alphabetic( + NumeralSystem::UpperLatin => bijective( + f, &[ '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', ], - n, + self.number, ), - Self::HiraganaAiueo => alphabetic( + NumeralSystem::HiraganaAiueo => bijective( + f, &[ 'あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ', 'さ', 'し', 'す', 'せ', 'そ', 'た', 'ち', 'つ', 'て', 'と', 'な', 'に', @@ -301,9 +330,10 @@ impl NumeralSystem { 'め', 'も', 'や', 'ゆ', 'よ', 'ら', 'り', 'る', 'れ', 'ろ', 'わ', 'を', 'ん', ], - n, + self.number, ), - Self::HiraganaIroha => alphabetic( + NumeralSystem::HiraganaIroha => bijective( + f, &[ 'い', 'ろ', 'は', 'に', 'ほ', 'へ', 'と', 'ち', 'り', 'ぬ', 'る', 'を', 'わ', 'か', 'よ', 'た', 'れ', 'そ', 'つ', 'ね', 'な', 'ら', @@ -311,9 +341,10 @@ impl NumeralSystem { 'え', 'て', 'あ', 'さ', 'き', 'ゆ', 'め', 'み', 'し', 'ゑ', 'ひ', 'も', 'せ', 'す', ], - n, + self.number, ), - Self::KatakanaAiueo => alphabetic( + NumeralSystem::KatakanaAiueo => bijective( + f, &[ 'ア', 'イ', 'ウ', 'エ', 'オ', 'カ', 'キ', 'ク', 'ケ', 'コ', 'サ', 'シ', 'ス', 'セ', 'ソ', 'タ', 'チ', 'ツ', 'テ', 'ト', 'ナ', 'ニ', @@ -321,9 +352,10 @@ impl NumeralSystem { 'メ', 'モ', 'ヤ', 'ユ', 'ヨ', 'ラ', 'リ', 'ル', 'レ', 'ロ', 'ワ', 'ヲ', 'ン', ], - n, + self.number, ), - Self::KatakanaIroha => alphabetic( + NumeralSystem::KatakanaIroha => bijective( + f, &[ 'イ', 'ロ', 'ハ', 'ニ', 'ホ', 'ヘ', 'ト', 'チ', 'リ', 'ヌ', 'ル', 'ヲ', 'ワ', 'カ', 'ヨ', 'タ', 'レ', 'ソ', 'ツ', 'ネ', 'ナ', 'ラ', @@ -331,31 +363,35 @@ impl NumeralSystem { 'エ', 'テ', 'ア', 'サ', 'キ', 'ユ', 'メ', 'ミ', 'シ', 'ヱ', 'ヒ', 'モ', 'セ', 'ス', ], - n, + self.number, ), - Self::KoreanJamo => alphabetic( + NumeralSystem::KoreanJamo => bijective( + f, &[ 'ㄱ', 'ㄴ', 'ㄷ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅅ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ', ], - n, + self.number, ), - Self::KoreanSyllable => alphabetic( + NumeralSystem::KoreanSyllable => bijective( + f, &[ '가', '나', '다', '라', '마', '바', '사', '아', '자', '차', '카', '타', '파', '하', ], - n, + self.number, ), - Self::BengaliLetter => alphabetic( + NumeralSystem::BengaliLetter => bijective( + f, &[ 'ক', 'খ', 'গ', 'ঘ', 'ঙ', 'চ', 'ছ', 'জ', 'ঝ', 'ঞ', 'ট', 'ঠ', 'ড', 'ঢ', 'ণ', 'ত', 'থ', 'দ', 'ধ', 'ন', 'প', 'ফ', 'ব', 'ভ', 'ম', 'য', 'র', 'ল', 'শ', 'ষ', 'স', 'হ', ], - n, + self.number, ), - Self::CircledNumber => fixed( + NumeralSystem::CircledNumber => fixed( + f, &[ '⓪', '①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩', '⑪', '⑫', '⑬', '⑭', '⑮', '⑯', '⑰', '⑱', '⑲', '⑳', '㉑', '㉒', '㉓', '㉔', '㉕', @@ -363,50 +399,74 @@ impl NumeralSystem { '㊲', '㊳', '㊴', '㊵', '㊶', '㊷', '㊸', '㊹', '㊺', '㊻', '㊼', '㊽', '㊾', '㊿', ], - n, + self.number, + ), + NumeralSystem::DoubleCircledNumber => fixed( + f, + &['0', '⓵', '⓶', '⓷', '⓸', '⓹', '⓺', '⓻', '⓼', '⓽', '⓾'], + self.number, ), - Self::DoubleCircledNumber => { - fixed(&['0', '⓵', '⓶', '⓷', '⓸', '⓹', '⓺', '⓻', '⓼', '⓽', '⓾'], n) - } - Self::LowerSimplifiedChinese => from_u64_to_chinese_ten_thousand( - ChineseVariant::Simple, - ChineseCase::Lower, - n, - ) - .into(), - Self::UpperSimplifiedChinese => from_u64_to_chinese_ten_thousand( - ChineseVariant::Simple, - ChineseCase::Upper, - n, - ) - .into(), - Self::LowerTraditionalChinese => from_u64_to_chinese_ten_thousand( - ChineseVariant::Traditional, - ChineseCase::Lower, - n, - ) - .into(), - Self::UpperTraditionalChinese => from_u64_to_chinese_ten_thousand( - ChineseVariant::Traditional, - ChineseCase::Upper, - n, - ) - .into(), + NumeralSystem::LowerSimplifiedChinese => write!( + f, + "{}", + from_u64_to_chinese_ten_thousand( + ChineseVariant::Simple, + ChineseCase::Lower, + self.number, + ) + ), + NumeralSystem::UpperSimplifiedChinese => write!( + f, + "{}", + from_u64_to_chinese_ten_thousand( + ChineseVariant::Simple, + ChineseCase::Upper, + self.number, + ) + ), + NumeralSystem::LowerTraditionalChinese => write!( + f, + "{}", + from_u64_to_chinese_ten_thousand( + ChineseVariant::Traditional, + ChineseCase::Lower, + self.number, + ) + ), + NumeralSystem::UpperTraditionalChinese => write!( + f, + "{}", + from_u64_to_chinese_ten_thousand( + ChineseVariant::Traditional, + ChineseCase::Upper, + self.number, + ) + ), - Self::EasternArabic => { - numeric(&['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'], n) - } - Self::EasternArabicPersian => { - numeric(&['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'], n) - } - Self::DevanagariNumber => { - numeric(&['०', '१', '२', '३', '४', '५', '६', '७', '८', '९'], n) - } - Self::BengaliNumber => { - numeric(&['০', '১', '২', '৩', '৪', '৫', '৬', '৭', '৮', '৯'], n) + NumeralSystem::EasternArabic => positional( + f, + &['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'], + self.number, + ), + NumeralSystem::EasternArabicPersian => positional( + f, + &['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'], + self.number, + ), + NumeralSystem::DevanagariNumber => positional( + f, + &['०', '१', '२', '३', '४', '५', '६', '७', '८', '९'], + self.number, + ), + NumeralSystem::BengaliNumber => positional( + f, + &['০', '১', '২', '৩', '৪', '৫', '৬', '৭', '৮', '৯'], + self.number, + ), + NumeralSystem::Symbol => { + symbolic(f, &['*', '†', '‡', '§', '¶', '‖'], self.number) } - Self::Symbol => symbolic(&['*', '†', '‡', '§', '¶', '‖'], n), } } } @@ -434,30 +494,30 @@ impl NumeralSystem { /// ``` /// /// This is the start of the familiar Roman numeral system. -fn additive(symbols: &[(&str, u64)], mut n: u64) -> EcoString { +fn additive( + f: &mut Formatter<'_>, + symbols: &[(&str, u64)], + mut n: u64, +) -> std::fmt::Result { if n == 0 { if let Some(&(symbol, 0)) = symbols.last() { - return symbol.into(); + return write!(f, "{}", symbol); } - return '0'.into(); + return write!(f, "0"); } - let mut s = EcoString::new(); for (symbol, weight) in symbols { if *weight == 0 || *weight > n { continue; } let reps = n / weight; for _ in 0..reps { - s.push_str(symbol); + write!(f, "{}", symbol)? } n -= weight * reps; - if n == 0 { - return s; - } } - s + Ok(()) } /// Formats a number using a big-endian @@ -479,18 +539,21 @@ fn additive(symbols: &[(&str, u64)], mut n: u64) -> EcoString { /// ``` /// /// A similar system is commonly used in spreadsheet software. -fn alphabetic(symbols: &[char], mut n: u64) -> EcoString { - let n_digits = symbols.len() as u64; +fn bijective(f: &mut Formatter<'_>, symbols: &[char], mut n: u64) -> std::fmt::Result { + let radix = symbols.len() as u64; if n == 0 { - return '-'.into(); + return write!(f, "-"); } - let mut s = EcoString::new(); + let mut digits = Vec::new(); while n != 0 { n -= 1; - s.push(symbols[(n % n_digits) as usize]); - n /= n_digits; + digits.push(symbols[(n % radix) as usize]); + n /= radix; + } + for digit in digits.iter().rev() { + write!(f, "{}", digit)? } - s.chars().rev().collect() + Ok(()) } /// Formats a number using the symbols provided, defaulting to the arabic @@ -507,12 +570,13 @@ fn alphabetic(symbols: &[char], mut n: u64) -> EcoString { /// 4 => "4" /// ... /// ``` -fn fixed(symbols: &[char], n: u64) -> EcoString { +fn fixed(f: &mut Formatter<'_>, symbols: &[char], n: u64) -> std::fmt::Result { let n_digits = symbols.len() as u64; if n < n_digits { - return symbols[n as usize].into(); + write!(f, "{}", symbols[n as usize]) + } else { + write!(f, "{n}") } - eco_format!("{n}") } /// Formats a number using a big-endian @@ -532,17 +596,20 @@ fn fixed(symbols: &[char], n: u64) -> EcoString { /// ``` /// /// This is the familiar ternary numeral system. -fn numeric(symbols: &[char], mut n: u64) -> EcoString { - let n_digits = symbols.len() as u64; +fn positional(f: &mut Formatter<'_>, symbols: &[char], mut n: u64) -> std::fmt::Result { + let radix = symbols.len() as u64; if n == 0 { - return symbols[0].into(); + return write!(f, "{}", symbols[0]); } - let mut s = EcoString::new(); + let mut digits = Vec::new(); while n != 0 { - s.push(symbols[(n % n_digits) as usize]); - n /= n_digits; + digits.push(symbols[(n % radix) as usize]); + n /= radix; + } + for digit in digits.iter().rev() { + write!(f, "{}", digit)? } - s.chars().rev().collect() + Ok(()) } /// Formats a number using repeating symbols. @@ -560,11 +627,13 @@ fn numeric(symbols: &[char], mut n: u64) -> EcoString { /// 7 => "AAA" /// ... /// ``` -fn symbolic(symbols: &[char], n: u64) -> EcoString { +fn symbolic(f: &mut Formatter<'_>, symbols: &[char], n: u64) -> std::fmt::Result { let n_digits = symbols.len() as u64; if n == 0 { - return '-'.into(); + return write!(f, "-"); + } + for _ in 0..n.div_ceil(n_digits) { + write!(f, "{}", symbols[((n - 1) % n_digits) as usize])? } - EcoString::from(symbols[((n - 1) % n_digits) as usize]) - .repeat(n.div_ceil(n_digits) as usize) + Ok(()) } From 70887258587fdb532fc3304f7fb3386ec71714b9 Mon Sep 17 00:00:00 2001 From: Malo <57839069+MDLC01@users.noreply.github.com> Date: Mon, 11 Aug 2025 17:27:23 +0200 Subject: [PATCH 08/12] Add tests for Arabic numerals and Latin letters --- src/numeral_systems.rs | 53 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/numeral_systems.rs b/src/numeral_systems.rs index b17c547..0e48732 100644 --- a/src/numeral_systems.rs +++ b/src/numeral_systems.rs @@ -637,3 +637,56 @@ fn symbolic(f: &mut Formatter<'_>, symbols: &[char], n: u64) -> std::fmt::Result } Ok(()) } + +#[cfg(test)] +mod tests { + use crate::numeral_systems::NumeralSystem; + + #[test] + fn test_arabic_numerals() { + for n in 0..=9999 { + assert_eq!(NumeralSystem::Arabic.apply(n).to_string(), n.to_string()) + } + } + + #[test] + fn test_latin() { + let mut n = 1; + for c1 in 'a'..='z' { + assert_eq!(NumeralSystem::LowerLatin.apply(n).to_string(), format!("{c1}")); + assert_eq!( + NumeralSystem::UpperLatin.apply(n).to_string(), + format!("{c1}").to_uppercase(), + ); + n += 1 + } + for c2 in 'a'..='z' { + for c1 in 'a'..='z' { + assert_eq!( + NumeralSystem::LowerLatin.apply(n).to_string(), + format!("{c2}{c1}"), + ); + assert_eq!( + NumeralSystem::UpperLatin.apply(n).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!( + NumeralSystem::LowerLatin.apply(n).to_string(), + format!("{c3}{c2}{c1}"), + ); + assert_eq!( + NumeralSystem::UpperLatin.apply(n).to_string(), + format!("{c3}{c2}{c1}").to_uppercase(), + ); + n += 1 + } + } + } + } +} From a6823f2d4391f9c094289ca7644ce0dbe13b66b3 Mon Sep 17 00:00:00 2001 From: Malo <57839069+MDLC01@users.noreply.github.com> Date: Mon, 11 Aug 2025 17:28:43 +0200 Subject: [PATCH 09/12] Rewrite `positional` without allocation --- src/numeral_systems.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/numeral_systems.rs b/src/numeral_systems.rs index 0e48732..71fae15 100644 --- a/src/numeral_systems.rs +++ b/src/numeral_systems.rs @@ -597,17 +597,19 @@ fn fixed(f: &mut Formatter<'_>, symbols: &[char], n: u64) -> std::fmt::Result { /// /// This is the familiar ternary numeral system. fn positional(f: &mut Formatter<'_>, symbols: &[char], mut n: u64) -> std::fmt::Result { - let radix = symbols.len() as u64; if n == 0 { return write!(f, "{}", symbols[0]); } - let mut digits = Vec::new(); - while n != 0 { - digits.push(symbols[(n % radix) as usize]); - n /= radix; - } - for digit in digits.iter().rev() { - write!(f, "{}", digit)? + + let radix = symbols.len() as u64; + let size = n.ilog(radix) + 1; + // For a number of size 1, the MSD's place is the ones place, hence `- 1`. + let mut most_significant_digit_place = radix.pow(size - 1); + for _ in 0..size { + let most_significant_digit = n / most_significant_digit_place; + write!(f, "{}", symbols[most_significant_digit as usize])?; + n -= most_significant_digit * most_significant_digit_place; + most_significant_digit_place /= radix; } Ok(()) } From 216e7c6d1cacb2f2dd1d9bd4c1a48534be6f6c9e Mon Sep 17 00:00:00 2001 From: Malo <57839069+MDLC01@users.noreply.github.com> Date: Mon, 11 Aug 2025 17:29:22 +0200 Subject: [PATCH 10/12] Rewrite `bijective` to ensure exactly one allocation --- src/numeral_systems.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/numeral_systems.rs b/src/numeral_systems.rs index 71fae15..755fcb7 100644 --- a/src/numeral_systems.rs +++ b/src/numeral_systems.rs @@ -540,11 +540,12 @@ fn additive( /// /// A similar system is commonly used in spreadsheet software. fn bijective(f: &mut Formatter<'_>, symbols: &[char], mut n: u64) -> std::fmt::Result { - let radix = symbols.len() as u64; if n == 0 { return write!(f, "-"); } - let mut digits = Vec::new(); + + let radix = symbols.len() as u64; + let mut digits = Vec::with_capacity((n.ilog(radix) + 1) as usize); while n != 0 { n -= 1; digits.push(symbols[(n % radix) as usize]); From 78a3066fd57d2d4f063f29db931970d1f604e515 Mon Sep 17 00:00:00 2001 From: Malo <57839069+MDLC01@users.noreply.github.com> Date: Mon, 11 Aug 2025 17:38:59 +0200 Subject: [PATCH 11/12] Add test for Roman numerals --- src/numeral_systems.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/numeral_systems.rs b/src/numeral_systems.rs index 755fcb7..280884e 100644 --- a/src/numeral_systems.rs +++ b/src/numeral_systems.rs @@ -692,4 +692,24 @@ mod tests { } } } + + #[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!(&NumeralSystem::LowerRoman.apply(n as u64).to_string(), expect); + assert_eq!( + NumeralSystem::UpperRoman.apply(n as u64).to_string(), + expect.to_uppercase(), + ); + } + } } From fa3dd63896884b98874e78a4a63b5587539971b9 Mon Sep 17 00:00:00 2001 From: Malo <57839069+MDLC01@users.noreply.github.com> Date: Mon, 11 Aug 2025 21:08:34 +0200 Subject: [PATCH 12/12] Do not allocate in `bijective` Co-authored-by: T0mstone <39707032+t0mstone@users.noreply.github.com> --- src/numeral_systems.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/numeral_systems.rs b/src/numeral_systems.rs index 280884e..c3d24df 100644 --- a/src/numeral_systems.rs +++ b/src/numeral_systems.rs @@ -545,14 +545,19 @@ fn bijective(f: &mut Formatter<'_>, symbols: &[char], mut n: u64) -> std::fmt::R } let radix = symbols.len() as u64; - let mut digits = Vec::with_capacity((n.ilog(radix) + 1) as usize); - while n != 0 { - n -= 1; - digits.push(symbols[(n % radix) as usize]); - n /= radix; - } - for digit in digits.iter().rev() { - write!(f, "{}", digit)? + // 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 11...11 from `n` (this number contains `size - 1` ones and is + // represented here in base-`radix`). + n -= (radix.pow(size) - 1) / (radix - 1); + // For a number of size 1, the MSD's place is the ones place, hence `- 1`. + let mut most_significant_digit_place = radix.pow(size - 1); + for _ in 0..size { + let most_significant_digit = n / most_significant_digit_place; + write!(f, "{}", symbols[most_significant_digit as usize])?; + n -= most_significant_digit * most_significant_digit_place; + most_significant_digit_place /= radix; } Ok(()) }