num_ordinal/lib.rs
1/*! Ordinal number types
2
3Ordinal numbers (_first, second, third, ..._) are usually represented
4as 0-based or 1-based integers. In English and most other natural
5languages, they're represented as 1-based numbers:
6first = 1st, second = 2nd, third = 3rd and so on.
7However, most programming languages are zero-based, i.e. when getting
8the first element in array or list, the index is 0. This is also true for Rust.
9
10# Usage
11
12To make working with ordinal numbers more explicit and less error-prone,
13this library provides ordinal number types that can be converted to/from
14cardinal numbers while specifying if it is 0- or 1-based:
15
16```rust
17use num_ordinal::{Ordinal, Osize};
18
19// Osize is an ordinal usize
20let o = Osize::from0(3);
21assert_eq!(&o.to_string(), "4th");
22
23let o = Osize::from1(3);
24assert_eq!(&o.to_string(), "third");
25```
26
27There are also two convenience functions to create ordinal numbers
28when the return type can be inferred:
29
30```rust
31use num_ordinal::{Osize, ordinal0, ordinal1};
32
33// Osize is an ordinal usize
34let o: Osize = ordinal0(3);
35assert_eq!(&o.to_string(), "4th");
36
37let o: Osize = ordinal1(3);
38assert_eq!(&o.to_string(), "third");
39```
40
41And [a macro](ordinal):
42
43```rust
44use num_ordinal::{O32, ordinal};
45
46// type is inferred:
47let o: O32 = ordinal!(4-th);
48
49// type can also be specified:
50let o = ordinal!(4-th O32);
51```
52
53# Implemented traits
54
55Ordinal numbers implement a number of traits, so they can be
56compared, hashed, copied and formatted. Also, you can add or
57subtract an integer from an ordinal number:
58
59```rust
60use num_ordinal::ordinal;
61
62assert_eq!(ordinal!(5-th O32) - 3, ordinal!(second O32));
63```
64
65Subtracting an ordinal from an ordinal produces an integer:
66
67```rust
68use num_ordinal::ordinal;
69
70assert_eq!(ordinal!(5-th O32) - ordinal!(second O32), 3);
71```
72
73The default value is _first_.
74
75# Features
76
77* `serde`: Implement `Serialize` and `Deserialize` for ordinals
78
79# License
80
81MIT
82*/
83
84#[cfg(feature = "serde")]
85mod serde_impl;
86
87use std::fmt;
88use std::ops::{Add, Sub};
89
90/// [Ordinal] number represented by [usize]
91#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, Default)]
92#[repr(transparent)]
93pub struct Osize(usize);
94
95/// [Ordinal] number represented by [u128]
96#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, Default)]
97#[repr(transparent)]
98pub struct O128(u128);
99
100/// [Ordinal] number represented by [u64]
101#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, Default)]
102#[repr(transparent)]
103pub struct O64(u64);
104
105/// [Ordinal] number represented by [u32]
106#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, Default)]
107#[repr(transparent)]
108pub struct O32(u32);
109
110/// [Ordinal] number represented by [u16]
111#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, Default)]
112#[repr(transparent)]
113pub struct O16(u16);
114
115/// [Ordinal] number represented by [u8]
116#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, Default)]
117#[repr(transparent)]
118pub struct O8(u8);
119
120/// An ordinal number type
121///
122/// See the [module-level documentation](index.html) for more.
123pub trait Ordinal:
124 Sized
125 + Eq
126 + PartialEq
127 + Ord
128 + PartialOrd
129 + std::hash::Hash
130 + Clone
131 + Copy
132 + Default
133 + fmt::Display
134 + fmt::Debug
135{
136 /// This type by which this ordinal type is represented
137 type IntegerType: Copy + fmt::Display;
138
139 /// The first ordinal number
140 fn first() -> Self;
141
142 /// Computes the ordinal number that comes after this one
143 fn next(self) -> Self;
144
145 /// Returns the equivalent integer assuming the ordinal number is 0-based
146 fn into0(self) -> Self::IntegerType;
147
148 /// Returns the equivalent integer assuming the ordinal number is 1-based
149 fn into1(self) -> Self::IntegerType;
150
151 /// Tries to convert an integer to a 0-based ordinal number.
152 ///
153 /// It returns [None] if the provided number is the highest number of that integer type.
154 /// This fails because that number can't be incremented by 1.
155 fn try_from0(t: Self::IntegerType) -> Option<Self>;
156
157 /// Tries to convert an integer to a 1-based ordinal number.
158 ///
159 /// It returns [None] if the provided number is 0.
160 fn try_from1(t: Self::IntegerType) -> Option<Self>;
161
162 /// Converts an integer to a 0-based ordinal number.
163 ///
164 /// ### Panics
165 ///
166 /// Panics if the provided number is the highest number of that integer type.
167 /// This fails because that number can't be incremented by 1.
168 fn from0(t: Self::IntegerType) -> Self {
169 Self::try_from0(t).unwrap_or_else(|| panic!("value {} is too big for this ordinal type", t))
170 }
171
172 /// Converts an integer to a 1-based ordinal number.
173 ///
174 /// ### Panics
175 ///
176 /// Panics if the provided number is 0.
177 fn from1(t: Self::IntegerType) -> Self {
178 Self::try_from1(t).expect("0 is not a valid 1-based ordinal.")
179 }
180}
181
182macro_rules! impl_ordinal {
183 ($t:ident, $int:ident) => {
184 impl Ordinal for $t {
185 type IntegerType = $int;
186
187 fn first() -> Self {
188 Self(0)
189 }
190
191 fn next(self) -> Self {
192 Self::from0(self.0 + 1)
193 }
194
195 fn into0(self) -> Self::IntegerType {
196 self.0
197 }
198
199 fn into1(self) -> Self::IntegerType {
200 self.0 + 1
201 }
202
203 fn try_from0(t: Self::IntegerType) -> Option<Self> {
204 match t {
205 $int::MAX => None,
206 _ => Some($t(t)),
207 }
208 }
209
210 fn try_from1(t: Self::IntegerType) -> Option<Self> {
211 t.checked_sub(1).map($t)
212 }
213 }
214
215 impl fmt::Debug for $t {
216 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217 match self.0 + 1 {
218 1 => write!(f, "first"),
219 2 => write!(f, "second"),
220 3 => write!(f, "third"),
221 n => {
222 let two_digits = n % 100;
223 let digit = two_digits % 10;
224 if digit == 1 && two_digits != 11 {
225 write!(f, "{}st", n)
226 } else if digit == 2 && two_digits != 12 {
227 write!(f, "{}nd", n)
228 } else if digit == 3 && two_digits != 13 {
229 write!(f, "{}rd", n)
230 } else {
231 write!(f, "{}th", n)
232 }
233 }
234 }
235 }
236 }
237
238 impl fmt::Display for $t {
239 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240 write!(f, "{:?}", self)
241 }
242 }
243
244 impl Add<$int> for $t {
245 type Output = $t;
246
247 fn add(self, rhs: $int) -> Self::Output {
248 Self::from0(self.0 + rhs)
249 }
250 }
251
252 impl Sub<$int> for $t {
253 type Output = $t;
254
255 fn sub(self, rhs: $int) -> Self::Output {
256 Self::from0(self.0 - rhs)
257 }
258 }
259
260 impl Sub<$t> for $t {
261 type Output = $int;
262
263 fn sub(self, rhs: $t) -> Self::Output {
264 self.0 - rhs.0
265 }
266 }
267 };
268}
269
270impl_ordinal!(Osize, usize);
271impl_ordinal!(O128, u128);
272impl_ordinal!(O64, u64);
273impl_ordinal!(O32, u32);
274impl_ordinal!(O16, u16);
275impl_ordinal!(O8, u8);
276
277/// Creates a 1-based ordinal number. For example, `ordinal1(4)` is the 4th ordinal number.
278pub fn ordinal1<O: Ordinal>(n: O::IntegerType) -> O {
279 O::from1(n)
280}
281
282/// Creates a 0-based ordinal number. For example, `ordinal0(4)` is the 5th ordinal number.
283pub fn ordinal0<O: Ordinal>(n: O::IntegerType) -> O {
284 O::from0(n)
285}
286
287/// Creates a 1-based ordinal number. Examples:
288///
289/// ```
290/// use num_ordinal::{O32, ordinal};
291///
292/// let mut o: O32 = ordinal!(first);
293/// o = ordinal!(second);
294/// o = ordinal!(third);
295///
296/// // Other numbers must use the following syntax:
297/// o = ordinal!(4-th);
298/// // the dash can be omitted, but then a space is required to make the Rust parser happy:
299/// o = ordinal!(4 th);
300/// // alternatively, a dot can be written after the number:
301/// o = ordinal!(4 .);
302///
303/// // When necessary, the type can be ascribed:
304/// let o = ordinal!(5-th O32);
305/// ```
306///
307/// Note that only `first`, `second` and `third` can be written as a full word:
308///
309/// ```compile_fail
310/// use num_ordinal::{O32, ordinal};
311///
312/// // doesn't compile!
313/// let _: O32 = ordinal!(fifth);
314/// ```
315#[macro_export]
316macro_rules! ordinal {
317 (first $($ty:ident)?) => {
318 $crate::ordinal1 $(::<$crate::$ty>)? (1)
319 };
320 (second $($ty:ident)?) => {
321 $crate::ordinal1 $(::<$crate::$ty>)? (2)
322 };
323 (third $($ty:ident)?) => {
324 $crate::ordinal1 $(::<$crate::$ty>)? (3)
325 };
326 ($n:literal $(-)? $suffix:ident $($ty:ident)?) => {
327 $crate::ordinal1 $(::<$crate::$ty>)? ($n)
328 };
329 ($n:literal . $($ty:ident)?) => {
330 $crate::ordinal1 $(::<$crate::$ty>)? ($n)
331 };
332}
333
334#[cfg(test)]
335mod tests {
336 use super::*;
337
338 #[test]
339 fn first_from0() {
340 let first_o_from0 = Osize::from0(0);
341
342 assert_eq!(&first_o_from0.to_string(), "first");
343 }
344
345 #[test]
346 fn first_from1() {
347 let first_o_from1 = Osize::from1(1);
348 assert_eq!(&first_o_from1.to_string(), "first");
349 }
350
351 #[test]
352 fn second_from0() {
353 let second_o_from0 = Osize::from0(1);
354 assert_eq!(&second_o_from0.to_string(), "second");
355 }
356
357 #[test]
358 fn second_from1() {
359 let second_o_from1 = Osize::from1(2);
360
361 assert_eq!(&second_o_from1.to_string(), "second");
362 }
363
364 #[test]
365 fn third_from0() {
366 let third_o_from0 = Osize::from0(2);
367 assert_eq!(&third_o_from0.to_string(), "third");
368 }
369
370 #[test]
371 fn third_from1() {
372 let third_o_from1 = Osize::from1(3);
373 assert_eq!(&third_o_from1.to_string(), "third");
374 }
375
376 #[test]
377 fn fourth_from0() {
378 let fourth_o_from0 = Osize::from0(3);
379 assert_eq!(&fourth_o_from0.to_string(), "4th");
380 }
381
382 #[test]
383 fn fourth_from1() {
384 let fourth_o_from1 = Osize::from1(4);
385 assert_eq!(&fourth_o_from1.to_string(), "4th");
386 }
387}