1use std::convert::TryFrom;
6use std::fmt::{self, Display};
7use std::ops::Range;
8use std::sync::LazyLock;
9
10use num::bigint::BigInt;
11use num::rational::BigRational;
12use num::{FromPrimitive, Integer, One, Signed, ToPrimitive, Zero};
13use num_traits::Float;
14
15use crate::defaults::{FloatType, UnsignedIntegerType};
16use crate::error::{Error, Result};
17
18macro_rules! define_ordinals {
19 (
20 $(#[$meta:meta])*
21 $vis:vis enum $name:ident {
22 $($variant:ident => ($display:expr, $lower:expr)),* $(,)?
23 }
24 ) => {
25 $(#[$meta])*
26 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
27 #[repr(u8)]
28 $vis enum $name {
29 $($variant),*
30 }
31
32 impl TryFrom<UnsignedIntegerType> for $name {
33 type Error = crate::error::Error;
34 fn try_from(value: UnsignedIntegerType) -> Result<Self, Self::Error> {
35 const VARIANTS: &[$name] = &[$($name::$variant),*];
36 let idx = value as usize;
37 if idx < VARIANTS.len() {
38 Ok(VARIANTS[idx])
39 } else {
40 Err(Error::Ordinal(format!(
41 "Invalid {} value: {}",
42 stringify!($name),
43 value
44 )))
45 }
46 }
47 }
48
49 impl TryFrom<&str> for $name {
50 type Error = crate::error::Error;
51 fn try_from(value: &str) -> Result<Self, Self::Error> {
52 match value.to_lowercase().as_str() {
53 $(
54 $lower => Ok($name::$variant),
55 )*
56 _ => Err(Error::Ordinal(format!(
57 "Invalid {} string: {}",
58 stringify!($name),
59 value
60 ))),
61 }
62 }
63 }
64
65 impl Display for $name {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 let s = match self {
68 $(
69 $name::$variant => $display,
70 )*
71 };
72 write!(f, "{}", s)
73 }
74 }
75 };
76}
77
78define_ordinals! {
80 pub(crate) enum MusicOrdinals {
81 Zeroth => ("Zeroth", "zeroth"),
82 Unison => ("Unison", "unison"), Second => ("Second", "second"),
84 Third => ("Third", "third"),
85 Fourth => ("Fourth", "fourth"),
86 Fifth => ("Fifth", "fifth"),
87 Sixth => ("Sixth", "sixth"),
88 Seventh => ("Seventh", "seventh"),
89 Octave => ("Octave", "octave"), Ninth => ("Ninth", "ninth"),
91 Tenth => ("Tenth", "tenth"),
92 Eleventh => ("Eleventh", "eleventh"),
93 Twelfth => ("Twelfth", "twelfth"),
94 Thirteenth => ("Thirteenth", "thirteenth"),
95 Fourteenth => ("Fourteenth", "fourteenth"),
96 DoubleOctave=> ("Double-octave", "double-octave"), Sixteenth => ("Sixteenth", "sixteenth"),
98 Seventeenth => ("Seventeenth", "seventeenth"),
99 Eighteenth => ("Eighteenth", "eighteenth"),
100 Nineteenth => ("Nineteenth", "nineteenth"),
101 Twentieth => ("Twentieth", "twentieth"),
102 TwentyFirst => ("Twenty-first", "twenty-first"),
103 TripleOctave=> ("Triple-octave", "triple-octave"), }
105}
106
107pub(crate) const ORDINALS: [&str; 8] = [
109 "Zeroth", "First", "Second", "Third", "Fourth", "Fifth", "Sixth", "Seventh",
110];
111
112pub(crate) static MUSICAL_ORDINAL_STRINGS: LazyLock<[String; 23]> = LazyLock::new(|| {
113 std::array::from_fn(|i| {
114 let val: UnsignedIntegerType = i as UnsignedIntegerType;
115 let ordinal = MusicOrdinals::try_from(val).unwrap();
116 format!("{ordinal}")
117 })
118});
119
120pub(crate) static MUSICAL_ORDINAL_STRINGS_LOWER: LazyLock<[String; 23]> =
123 LazyLock::new(|| std::array::from_fn(|i| MUSICAL_ORDINAL_STRINGS[i].to_ascii_lowercase()));
124
125pub const DENOM_LIMIT: u64 = 65535;
128
129pub const ORDINAL_NAMES: [&str; 23] = [
132 "Zeroth",
133 "First",
134 "Second",
135 "Third",
136 "Fourth",
137 "Fifth",
138 "Sixth",
139 "Seventh",
140 "Eighth",
141 "Ninth",
142 "Tenth",
143 "Eleventh",
144 "Twelfth",
145 "Thirteenth",
146 "Fourteenth",
147 "Fifteenth",
148 "Sixteenth",
149 "Seventeenth",
150 "Eighteenth",
151 "Nineteenth",
152 "Twentieth",
153 "Twenty-first",
154 "Twenty-second",
155];
156
157pub const MUSIC_ORDINAL_NAMES: [&str; 23] = [
160 "Zeroth",
161 "Unison",
162 "Second",
163 "Third",
164 "Fourth",
165 "Fifth",
166 "Sixth",
167 "Seventh",
168 "Octave",
169 "Ninth",
170 "Tenth",
171 "Eleventh",
172 "Twelfth",
173 "Thirteenth",
174 "Fourteenth",
175 "Double-octave",
176 "Sixteenth",
177 "Seventeenth",
178 "Eighteenth",
179 "Nineteenth",
180 "Twentieth",
181 "Twenty-first",
182 "Triple-octave",
183];
184
185#[derive(Clone, Copy, Debug, PartialEq)]
188pub enum OffsetQL {
189 Float(FloatType),
191 Fraction {
193 numerator: i64,
195 denominator: i64,
197 },
198}
199
200impl OffsetQL {
201 pub fn to_f64(self) -> FloatType {
203 match self {
204 Self::Float(value) => value,
205 Self::Fraction {
206 numerator,
207 denominator,
208 } => numerator as FloatType / denominator as FloatType,
209 }
210 }
211}
212
213#[derive(Clone, Debug, PartialEq)]
216pub enum IntOrFloat {
217 Int(BigInt),
219 Float(FloatType),
221}
222
223pub fn is_close(a: FloatType, b: FloatType, abs_tol: FloatType) -> bool {
233 if a == b {
234 return true;
235 }
236 if a.is_infinite() || b.is_infinite() {
237 return false;
238 }
239 let difference = (b - a).abs();
240 difference <= (1e-9 * b).abs() || difference <= (1e-9 * a).abs() || difference <= abs_tol
241}
242
243pub fn float_repr(value: FloatType) -> String {
256 if value.is_nan() {
257 return "nan".to_string();
258 }
259 if value.is_infinite() {
260 return if value > 0.0 { "inf" } else { "-inf" }.to_string();
261 }
262 let scientific = format!("{value:e}");
263 let (mantissa, exponent) = scientific
264 .split_once('e')
265 .unwrap_or((scientific.as_str(), "0"));
266 let exponent: i32 = exponent.parse().unwrap_or(0);
267 let (sign, mantissa) = match mantissa.strip_prefix('-') {
268 Some(rest) => ("-", rest),
269 None => ("", mantissa),
270 };
271 let digits: String = mantissa.chars().filter(char::is_ascii_digit).collect();
272 if (-4..16).contains(&exponent) {
273 let point = exponent + 1;
274 if point <= 0 {
275 let zeros = "0".repeat(point.unsigned_abs() as usize);
276 return format!("{sign}0.{zeros}{digits}");
277 }
278 let point = point as usize;
279 if digits.len() <= point {
280 let zeros = "0".repeat(point - digits.len());
281 return format!("{sign}{digits}{zeros}.0");
282 }
283 return format!("{sign}{}.{}", &digits[..point], &digits[point..]);
284 }
285 let rest = if digits.len() > 1 {
286 format!(".{}", &digits[1..])
287 } else {
288 String::new()
289 };
290 let exponent_sign = if exponent < 0 { '-' } else { '+' };
291 format!(
292 "{sign}{}{rest}e{exponent_sign}{:02}",
293 &digits[..1],
294 exponent.unsigned_abs()
295 )
296}
297
298pub(crate) fn round_digits(value: FloatType, digits: usize) -> FloatType {
301 if !value.is_finite() {
302 return value;
303 }
304 format!("{value:.digits$}").parse().unwrap_or(value)
305}
306
307pub(crate) fn float_divmod(x: FloatType, y: FloatType) -> (FloatType, FloatType) {
310 let mut modulo = x % y;
311 let mut division = (x - modulo) / y;
312 if modulo != 0.0 {
313 if (y < 0.0) != (modulo < 0.0) {
314 modulo += y;
315 division -= 1.0;
316 }
317 } else {
318 modulo = 0.0f64.copysign(y);
319 }
320 if division == 0.0 {
321 return (0.0f64.copysign(x / y), modulo);
322 }
323 let mut floored = division.floor();
324 if division - floored > 0.5 {
325 floored += 1.0;
326 }
327 (floored, modulo)
328}
329
330pub fn float_ratio(value: FloatType) -> Option<(BigInt, BigInt)> {
341 if !value.is_finite() {
342 return None;
343 }
344 let (mantissa, exponent, sign) = Float::integer_decode(value);
345 if mantissa == 0 {
346 return Some((BigInt::zero(), BigInt::one()));
347 }
348 let zeros = mantissa.trailing_zeros();
349 let mantissa = BigInt::from(mantissa >> zeros) * BigInt::from(sign);
350 let exponent = i32::from(exponent) + zeros as i32;
351 if exponent >= 0 {
352 return Some((mantissa << exponent as usize, BigInt::one()));
353 }
354 Some((mantissa, BigInt::one() << exponent.unsigned_abs() as usize))
355}
356
357pub fn limit_denominator(
375 numerator: &BigInt,
376 denominator: &BigInt,
377 limit: &BigInt,
378) -> Result<(BigInt, BigInt)> {
379 if *limit < BigInt::one() {
380 return Err(Error::Value(
381 "max_denominator should be at least 1".to_string(),
382 ));
383 }
384 if denominator <= limit {
385 return Ok((numerator.clone(), denominator.clone()));
386 }
387 let (mut p0, mut q0, mut p1, mut q1) =
388 (BigInt::zero(), BigInt::one(), BigInt::one(), BigInt::zero());
389 let (mut n, mut d) = (numerator.clone(), denominator.clone());
390 loop {
391 if d.is_zero() {
393 return Ok((p1, q1));
394 }
395 let a = n.div_floor(&d);
396 let q2 = &q0 + &a * &q1;
397 if q2 > *limit {
398 break;
399 }
400 let p2 = &p0 + &a * &p1;
401 (p0, q0, p1, q1) = (p1, q1, p2, q2);
402 let next = &n - &a * &d;
403 n = d;
404 d = next;
405 }
406 let k = (limit - &q0).div_floor(&q1);
407 let far_n = &p0 + &k * &p1;
408 let far_d = &q0 + &k * &q1;
409 let far_off = (&far_n * denominator - numerator * &far_d).abs();
410 let near_off = (&p1 * denominator - numerator * &q1).abs();
411 if far_off * (denominator * &q1) - near_off * (denominator * &far_d) >= BigInt::zero() {
412 return Ok((p1, q1));
413 }
414 Ok((far_n, far_d))
415}
416
417fn is_power_of_two(value: &BigInt) -> bool {
418 value.is_positive() && (value & (value - BigInt::one())).is_zero()
419}
420
421pub fn op_frac(value: FloatType) -> Result<OffsetQL> {
444 let (numerator, denominator) = float_ratio(value).ok_or_else(|| {
445 let which = if value.is_nan() { "NaN" } else { "Infinity" };
446 Error::Value(format!("cannot convert {which} to integer ratio"))
447 })?;
448 let limit = BigInt::from(DENOM_LIMIT);
449 if denominator <= limit {
450 return Ok(OffsetQL::Float(value));
451 }
452 let (numerator, denominator) = limit_denominator(&numerator, &denominator, &limit)?;
453 let numerator = numerator.to_i64().unwrap_or(i64::MAX);
456 let denominator = denominator.to_i64().unwrap_or(1);
457 if denominator & (denominator - 1) == 0 {
458 return Ok(OffsetQL::Float(
459 numerator as FloatType / denominator as FloatType,
460 ));
461 }
462 Ok(OffsetQL::Fraction {
463 numerator,
464 denominator,
465 })
466}
467
468pub fn is_binary_denominator(denominator: &BigInt) -> bool {
471 is_power_of_two(denominator)
472}
473
474pub fn num_to_int_or_float(value: FloatType) -> Result<IntOrFloat> {
489 let rounded = whole(value.round_ties_even())?;
490 let back = rounded.to_f64().unwrap_or(FloatType::INFINITY);
491 if is_close(back, value, 1e-6) {
492 return Ok(IntOrFloat::Int(rounded));
493 }
494 Ok(IntOrFloat::Float(value))
495}
496
497fn whole(value: FloatType) -> Result<BigInt> {
499 if value.is_nan() {
500 return Err(Error::Value(
501 "cannot convert float NaN to integer".to_string(),
502 ));
503 }
504 BigInt::from_f64(value.trunc())
505 .ok_or_else(|| Error::Value("cannot convert float infinity to integer".to_string()))
506}
507
508fn fraction_text(numerator: &BigInt, denominator: &BigInt) -> String {
510 if denominator.is_one() {
511 return numerator.to_string();
512 }
513 format!("{numerator}/{denominator}")
514}
515
516fn mixed_text(quotient: &BigInt, remainder: &BigRational) -> String {
517 let fraction = fraction_text(remainder.numer(), remainder.denom());
518 if !quotient.is_zero() {
519 if remainder.is_zero() {
520 return quotient.to_string();
521 }
522 return format!("{quotient} {fraction}");
523 }
524 if !remainder.is_zero() {
525 return fraction;
526 }
527 "0".to_string()
528}
529
530pub fn mixed_numeral(value: FloatType, limit: u64) -> Result<String> {
546 let (mut quotient, remainder) = float_divmod(value, 1.0);
547 let (numerator, denominator) = float_ratio(remainder)
548 .ok_or_else(|| Error::Value("cannot convert NaN to integer ratio".to_string()))?;
549 let (numerator, denominator) =
550 limit_denominator(&numerator, &denominator, &BigInt::from(limit))?;
551 let mut fraction = BigRational::new(numerator, denominator);
552 if quotient < -1.0 {
553 quotient += 1.0;
554 fraction = BigRational::one() - fraction;
555 } else if quotient == -1.0 {
556 quotient = 0.0;
557 fraction -= BigRational::one();
558 }
559 Ok(mixed_text(&whole(quotient)?, &fraction))
560}
561
562pub fn mixed_numeral_fraction(numerator: &BigInt, denominator: &BigInt) -> Result<String> {
578 if denominator.is_zero() {
579 return Err(Error::Value("Fraction(_, 0)".to_string()));
580 }
581 let value = BigRational::new(numerator.clone(), denominator.clone());
582 let quotient = whole(value.to_f64().unwrap_or(FloatType::NAN))?;
583 let mut remainder = &value - BigRational::from_integer(quotient.clone());
584 if quotient.is_negative() {
585 remainder = -remainder;
586 }
587 Ok(mixed_text("ient, &remainder))
588}
589
590pub fn round_to_half_integer(value: FloatType) -> Result<IntOrFloat> {
607 let (floor, fraction) = float_divmod(value, 1.0);
608 let floor = whole(floor)?;
609 if fraction < 0.25 {
610 return Ok(IntOrFloat::Int(floor));
611 }
612 if fraction < 0.75 {
613 let floor = floor.to_f64().unwrap_or(FloatType::INFINITY);
614 return Ok(IntOrFloat::Float(floor + 0.5));
615 }
616 Ok(IntOrFloat::Int(floor + 1))
617}
618
619pub fn add_float_precision(value: FloatType, grain: FloatType) -> Option<OffsetQL> {
633 [1.0 / 3.0, 2.0 / 3.0, 1.0 / 6.0, 5.0 / 6.0]
634 .into_iter()
635 .find(|candidate| is_close(value, *candidate, grain))
636 .and_then(|candidate| op_frac(candidate).ok())
637}
638
639pub fn str_trim_float(value: FloatType, max_digits: usize) -> Result<String> {
656 let written = format!("{value:.max_digits$}");
657 let point = written
658 .find('.')
659 .ok_or_else(|| Error::Value("substring not found".to_string()))?;
660 let bytes = written.as_bytes();
661 let mut length = written.len();
662 for index in (point + 2..written.len()).rev() {
663 if bytes[index] != b'0' {
664 break;
665 }
666 length -= 1;
667 }
668 Ok(written[..length].to_string())
669}
670
671#[derive(Clone, Copy, Debug, PartialEq)]
674pub struct NearestMultiple {
675 pub multiple: FloatType,
677 pub error: FloatType,
679 pub signed_error: FloatType,
681}
682
683pub fn nearest_multiple(value: FloatType, unit: FloatType) -> Result<NearestMultiple> {
700 if value < 0.0 {
701 return Err(Error::Value(format!(
702 "n ({}) is less than zero. Thus, cannot find the nearest multiple for a value \
703 less than the unit, {}",
704 float_repr(value),
705 float_repr(unit)
706 )));
707 }
708 let multiple = (value / unit).floor() + 0.0;
710 let low = unit * multiple;
711 let high = unit * (multiple + 1.0);
712 if low >= value && value >= high {
713 return Err(Error::Value(format!(
714 "cannot place n between multiples: {}, {}",
715 float_repr(low),
716 float_repr(high)
717 )));
718 }
719 if low <= value && value <= low + unit / 2.0 {
720 let error = round_digits(value - low, 7);
721 return Ok(NearestMultiple {
722 multiple: low,
723 error,
724 signed_error: error,
725 });
726 }
727 Ok(NearestMultiple {
728 multiple: high,
729 error: round_digits(high - value, 7),
730 signed_error: round_digits(value - high, 7),
731 })
732}
733
734const DOT_MULTIPLIERS: [FloatType; 9] = [
736 1.0, 1.5, 1.75, 1.875, 1.9375, 1.96875, 1.984375, 1.9921875, 1.99609375,
737];
738
739pub fn dot_multiplier(dots: u32) -> FloatType {
750 if let Some(found) = DOT_MULTIPLIERS.get(dots as usize) {
751 return *found;
752 }
753 let dots = FloatType::from(dots);
754 (2.0f64.powf(dots + 1.0) - 1.0) / 2.0f64.powf(dots)
755}
756
757pub fn decimal_to_tuplet(value: FloatType) -> Result<(i128, i128)> {
773 if value <= 0.0 {
774 return Err(Error::Value("number must be greater than zero".to_string()));
775 }
776 let flipped = value < 1.0;
777 let value = if flipped { 1.0 / value } else { value };
778 let multiplier = value.trunc();
779 let working = value / multiplier;
780 let (numerator, denominator) =
781 simple_fraction(working).ok_or_else(|| Error::Value("No such luck".to_string()))?;
782 let scaled = FloatType::from(numerator) * multiplier;
783 let divisor = num::integer::gcd(scaled as i128, i128::from(denominator));
784 let numerator = (scaled / divisor as FloatType) as i128;
785 let denominator = (FloatType::from(denominator) / divisor as FloatType) as i128;
786 if flipped {
787 return Ok((denominator, numerator));
788 }
789 Ok((numerator, denominator))
790}
791
792fn simple_fraction(value: FloatType) -> Option<(u32, u32)> {
795 for index in 1..1000u32 {
796 for numerator in index..index * 2 {
797 if is_close(
798 value,
799 FloatType::from(numerator) / FloatType::from(index),
800 1e-7,
801 ) {
802 return Some((numerator, index));
803 }
804 }
805 }
806 None
807}
808
809pub fn unit_normalize_proportion(values: &[FloatType]) -> Result<Vec<FloatType>> {
823 let mut sum = 0.0;
824 for value in values {
825 if *value < 0.0 {
826 return Err(Error::Value("value members must be positive".to_string()));
827 }
828 sum += value;
829 }
830 Ok(values.iter().map(|value| value / sum).collect())
831}
832
833pub fn unit_boundary_proportion(values: &[FloatType]) -> Result<Vec<(FloatType, FloatType)>> {
851 let shares = unit_normalize_proportion(values)?;
852 let mut bounds = Vec::with_capacity(shares.len());
853 let mut sum = 0.0;
854 for (index, share) in shares.iter().enumerate() {
855 if index == shares.len() - 1 {
856 bounds.push((sum, 1.0));
857 break;
858 }
859 bounds.push((sum, sum + share));
860 sum += share;
861 }
862 Ok(bounds)
863}
864
865pub fn weighted_index(weights: &[FloatType], draw: FloatType) -> Result<usize> {
881 let bounds = unit_boundary_proportion(weights)?;
882 let found = bounds
883 .iter()
884 .position(|(low, high)| *low <= draw && draw < *high);
885 Ok(found.unwrap_or(bounds.len().saturating_sub(1)))
886}
887
888pub fn approximate_gcd(values: &[FloatType], grain: FloatType) -> Result<FloatType> {
905 let lowest = values
906 .iter()
907 .copied()
908 .reduce(FloatType::min)
909 .ok_or_else(|| Error::Value("min() iterable argument is empty".to_string()))?;
910 let divides = values.iter().all(|value| {
911 let adjusted = value / lowest;
912 is_close(adjusted - adjusted.trunc(), 0.0, grain)
913 });
914 if divides {
915 return Ok(lowest);
916 }
917 let divisions: Vec<Vec<FloatType>> = values
918 .iter()
919 .map(|value| (1..=16).map(|d| value / FloatType::from(d)).collect())
920 .collect();
921 divisions
922 .iter()
923 .flatten()
924 .copied()
925 .filter(|candidate| {
926 divisions
927 .iter()
928 .all(|row| row.iter().any(|x| is_close(*x, *candidate, grain)))
929 })
930 .reduce(FloatType::max)
931 .ok_or_else(|| Error::Value("cannot find a common divisor".to_string()))
932}
933
934pub fn contiguous_list<T>(values: &[T]) -> bool
945where
946 T: Copy + PartialEq + std::ops::Add<Output = T> + One,
947{
948 values.windows(2).all(|pair| pair[1] == pair[0] + T::one())
949}
950
951pub fn contiguous_runs<T>(sorted: &[T]) -> Vec<Range<usize>>
960where
961 T: Copy + PartialEq + std::ops::Add<Output = T> + One,
962{
963 let mut runs = Vec::new();
964 let mut start = 0;
965 for index in 1..=sorted.len() {
966 let ends = index == sorted.len() || sorted[index] != sorted[index - 1] + T::one();
967 if ends {
968 runs.push(start..index);
969 start = index;
970 }
971 }
972 runs
973}
974
975pub fn from_roman(text: &str, strict_modern: bool) -> Result<i64> {
992 let upper = text.to_uppercase();
993 let value_of = |letter: char| match letter {
994 'M' => Some(1000),
995 'D' => Some(500),
996 'C' => Some(100),
997 'L' => Some(50),
998 'X' => Some(10),
999 'V' => Some(5),
1000 'I' => Some(1),
1001 _ => None,
1002 };
1003 let values: Vec<i64> = upper
1004 .chars()
1005 .map(value_of)
1006 .collect::<Option<_>>()
1007 .ok_or_else(|| Error::Value(format!("value is not a valid roman numeral: {upper}")))?;
1008 let mut sum = 0;
1009 for (index, value) in values.iter().enumerate() {
1010 let mut value = *value;
1011 if let Some(next) = values.get(index + 1).copied() {
1012 if next > value && [1, 10, 100].contains(&value) {
1013 if strict_modern && next >= value * 10 {
1014 return Err(Error::Value(format!(
1015 "input contains an invalid subtraction element (modern interpretation): \
1016 {text}"
1017 )));
1018 }
1019 value = -value;
1020 } else if next > value {
1021 return Err(Error::Value(format!(
1022 "input contains an invalid subtraction element: {text}"
1023 )));
1024 }
1025 }
1026 sum += value;
1027 }
1028 Ok(sum)
1029}
1030
1031pub fn to_roman(number: i64) -> Result<String> {
1045 if !(1..4000).contains(&number) {
1046 return Err(Error::Value(
1047 "Argument must be between 1 and 3999".to_string(),
1048 ));
1049 }
1050 const NUMERALS: [(i64, &str); 13] = [
1051 (1000, "M"),
1052 (900, "CM"),
1053 (500, "D"),
1054 (400, "CD"),
1055 (100, "C"),
1056 (90, "XC"),
1057 (50, "L"),
1058 (40, "XL"),
1059 (10, "X"),
1060 (9, "IX"),
1061 (5, "V"),
1062 (4, "IV"),
1063 (1, "I"),
1064 ];
1065 let mut rest = number;
1066 let mut written = String::new();
1067 for (value, numeral) in NUMERALS {
1068 let count = rest / value;
1069 written.push_str(&numeral.repeat(count as usize));
1070 rest -= value * count;
1071 }
1072 Ok(written)
1073}
1074
1075pub fn ordinal_abbreviation(value: i64, plural: bool) -> &'static str {
1087 let suffix = if (11..=13).contains(&value.rem_euclid(100)) {
1088 "th"
1089 } else {
1090 match value.rem_euclid(10) {
1091 1 => "st",
1092 2 => "nd",
1093 3 => "rd",
1094 _ => "th",
1095 }
1096 };
1097 if !plural || suffix == "st" {
1098 return suffix;
1099 }
1100 match suffix {
1101 "nd" => "nds",
1102 "rd" => "rds",
1103 _ => "ths",
1104 }
1105}
1106
1107pub fn ordinals_to_numbers() -> Vec<(String, usize)> {
1111 let mut entries = Vec::new();
1112 for (index, name) in ORDINAL_NAMES.iter().enumerate() {
1113 entries.push(((*name).to_string(), index));
1114 entries.push((name.to_lowercase(), index));
1115 let abbreviation = ordinal_abbreviation(index as i64, false);
1116 entries.push((format!("{index}{abbreviation}"), index));
1117 let music = MUSIC_ORDINAL_NAMES[index];
1118 if music != *name {
1119 entries.push((music.to_string(), index));
1120 entries.push((music.to_lowercase(), index));
1121 }
1122 }
1123 entries
1124}
1125
1126#[cfg(test)]
1127mod tests {
1128 use super::*;
1129 use std::convert::TryFrom;
1130
1131 #[test]
1132 fn floats_past_the_limit_snap_to_a_fraction() {
1133 assert_eq!(
1134 op_frac(0.123456789).unwrap(),
1135 OffsetQL::Fraction {
1136 numerator: 10,
1137 denominator: 81
1138 }
1139 );
1140 assert_eq!(op_frac(0.000001).unwrap(), OffsetQL::Float(0.0));
1141 assert_eq!(op_frac(-1.0 / 3.0).unwrap().to_f64(), -1.0 / 3.0);
1142 assert!(op_frac(f64::NAN).is_err());
1143 assert!(op_frac(f64::INFINITY).is_err());
1144 }
1145
1146 #[test]
1147 fn music21s_own_limit_matches_pythons() {
1148 let limited = limit_denominator(&100_001.into(), &300_001.into(), &65535.into()).unwrap();
1149 assert_eq!(limited, (BigInt::from(1), BigInt::from(3)));
1150 let (n, d) = float_ratio(1e-300).unwrap();
1151 let limited = limit_denominator(&n, &d, &65535.into()).unwrap();
1152 assert_eq!(limited, (BigInt::from(0), BigInt::from(1)));
1153 }
1154
1155 #[test]
1156 fn mixed_numerals_written_as_music21_writes_them() {
1157 assert_eq!(mixed_numeral(-1.333333, 65535).unwrap(), "-1 1/3");
1158 assert_eq!(mixed_numeral(0.0, 65535).unwrap(), "0");
1159 assert_eq!(mixed_numeral(-0.0, 65535).unwrap(), "0");
1160 assert_eq!(
1161 mixed_numeral(2.0000001, 10_000_000).unwrap(),
1162 "2 1/10000000"
1163 );
1164 let fraction = |n: i64, d: i64| mixed_numeral_fraction(&n.into(), &d.into()).unwrap();
1165 assert_eq!(fraction(-31, 7), "-4 3/7");
1166 assert_eq!(fraction(-1, 5), "-1/5");
1167 assert_eq!(fraction(1, 5), "1/5");
1168 }
1169
1170 #[test]
1171 fn floats_written_as_python_writes_them() {
1172 assert_eq!(float_repr(-0.5), "-0.5");
1173 assert_eq!(float_repr(23404.0), "23404.0");
1174 assert_eq!(float_repr(1e22), "1e+22");
1175 assert_eq!(float_repr(1.5e-7), "1.5e-07");
1176 assert_eq!(float_repr(0.0001), "0.0001");
1177 assert_eq!(float_repr(123456789012345.6), "123456789012345.6");
1178 assert_eq!(round_digits(0.35 - 0.25, 7), 0.1);
1179 }
1180
1181 #[test]
1182 fn halves_round_up_on_the_boundaries() {
1183 let int = |value: i64| IntOrFloat::Int(value.into());
1184 assert_eq!(round_to_half_integer(1.2).unwrap(), int(1));
1185 assert_eq!(round_to_half_integer(0.25).unwrap(), IntOrFloat::Float(0.5));
1186 assert_eq!(round_to_half_integer(0.75).unwrap(), int(1));
1187 assert_eq!(
1188 round_to_half_integer(-0.26).unwrap(),
1189 IntOrFloat::Float(-0.5)
1190 );
1191 assert!(round_to_half_integer(f64::INFINITY).is_err());
1192 }
1193
1194 #[test]
1195 fn nearest_multiples_and_their_errors() {
1196 let found = nearest_multiple(23404.0 - 0.0625, 0.125).unwrap();
1197 assert_eq!(
1198 (found.multiple, found.error, found.signed_error),
1199 (23403.875, 0.0625, 0.0625)
1200 );
1201 let error = nearest_multiple(-0.5, 0.125).unwrap_err();
1202 assert_eq!(
1203 error.to_string(),
1204 "Value error: n (-0.5) is less than zero. Thus, cannot find the nearest multiple \
1205 for a value less than the unit, 0.125"
1206 );
1207 }
1208
1209 #[test]
1212 fn the_multiple_of_negative_nought_is_unsigned() {
1213 let found = nearest_multiple(-0.0, 0.125).unwrap();
1214 assert!(found.multiple.is_sign_positive());
1215 assert!(found.error.is_sign_negative());
1216 }
1217
1218 #[test]
1219 fn numbers_and_strings_of_music21s_examples() {
1220 assert_eq!(str_trim_float(-5.0, 4).unwrap(), "-5.0");
1221 assert!(str_trim_float(1.0, 0).is_err());
1222 assert_eq!(decimal_to_tuplet(1.5).unwrap(), (3, 2));
1223 assert!(decimal_to_tuplet(-0.02).is_err());
1224 assert_eq!(dot_multiplier(3), 1.875);
1225 assert_eq!(dot_multiplier(10), 2047.0 / 1024.0);
1226 assert_eq!(approximate_gcd(&[2.0, 5.0, 10.0], 1e-4).unwrap(), 1.0);
1227 let third = approximate_gcd(&[5.0 / 3.0, 2.0 / 3.0, 5.0 / 6.0, 3.0 / 6.0], 1e-4).unwrap();
1228 assert_eq!(str_trim_float(third, 4).unwrap(), "0.1667");
1229 assert_eq!(
1230 num_to_int_or_float(1.00003).unwrap(),
1231 IntOrFloat::Float(1.00003)
1232 );
1233 assert!(unit_normalize_proportion(&[0.0, -2.0, -8.0]).is_err());
1234 assert_eq!(weighted_index(&[1.0, 0.0], 0.99).unwrap(), 0);
1235 assert_eq!(contiguous_runs(&[3, 3, 4]), [0..1, 1..3]);
1236 assert_eq!(contiguous_runs::<i64>(&[]), Vec::<Range<usize>>::new());
1237 }
1238
1239 #[test]
1240 fn roman_numerals_both_ways() {
1241 assert_eq!(from_roman("vii", false).unwrap(), 7);
1242 assert_eq!(from_roman("MCCCCLXXXIX", false).unwrap(), 1489);
1243 assert_eq!(
1244 from_roman("vx", false).unwrap_err().to_string(),
1245 "Value error: input contains an invalid subtraction element: vx"
1246 );
1247 assert_eq!(
1248 from_roman("ab", false).unwrap_err().to_string(),
1249 "Value error: value is not a valid roman numeral: AB"
1250 );
1251 assert_eq!(to_roman(7).unwrap(), "VII");
1252 assert!(to_roman(4000).is_err());
1253 }
1254
1255 #[test]
1256 fn ordinals_map_to_their_numbers() {
1257 let entries = ordinals_to_numbers();
1258 let find = |name: &str| entries.iter().find(|(key, _)| key == name).map(|(_, n)| *n);
1259 assert_eq!(find("unison"), Some(1));
1260 assert_eq!(find("1st"), Some(1));
1261 assert_eq!(find("Octave"), Some(8));
1262 assert_eq!(find("8th"), Some(8));
1263 assert_eq!(find("22nd"), Some(22));
1264 assert_eq!(
1265 &entries[..3],
1266 &[
1267 ("Zeroth".to_string(), 0),
1268 ("zeroth".to_string(), 0),
1269 ("0th".to_string(), 0)
1270 ]
1271 );
1272 }
1273
1274 #[test]
1275 fn test_music_ordinal_try_from_integer_valid() {
1276 let cases = [
1277 (0, MusicOrdinals::Zeroth),
1278 (1, MusicOrdinals::Unison),
1279 (2, MusicOrdinals::Second),
1280 (8, MusicOrdinals::Octave),
1281 (15, MusicOrdinals::DoubleOctave),
1282 (22, MusicOrdinals::TripleOctave),
1283 ];
1284 for (input, expected) in cases {
1285 let ordinal = MusicOrdinals::try_from(input).unwrap();
1286 assert_eq!(ordinal, expected);
1287 }
1288 }
1289
1290 #[test]
1291 fn test_music_ordinal_try_from_integer_invalid() {
1292 let result = MusicOrdinals::try_from(23);
1293 assert!(result.is_err());
1294 if let Err(Error::Ordinal(msg)) = result {
1295 assert!(msg.contains("Invalid MusicOrdinals value: 23"));
1296 } else {
1297 panic!("Expected Error::Ordinal error");
1298 }
1299 }
1300
1301 #[test]
1302 fn test_music_ordinal_try_from_str_valid() {
1303 let cases = [
1304 ("zeroth", MusicOrdinals::Zeroth),
1305 ("Unison", MusicOrdinals::Unison),
1306 ("SECOND", MusicOrdinals::Second),
1307 ("octave", MusicOrdinals::Octave),
1308 ("double-octave", MusicOrdinals::DoubleOctave),
1309 ("triple-octave", MusicOrdinals::TripleOctave),
1310 ];
1311 for (input, expected) in cases {
1312 let ordinal = MusicOrdinals::try_from(input).unwrap();
1313 assert_eq!(ordinal, expected);
1314 }
1315 }
1316
1317 #[test]
1318 fn test_music_ordinal_try_from_str_invalid() {
1319 let result = MusicOrdinals::try_from("invalid");
1320 assert!(result.is_err());
1321 if let Err(Error::Ordinal(msg)) = result {
1322 assert!(msg.contains("Invalid MusicOrdinals string: invalid"));
1323 } else {
1324 panic!("Expected Error::Ordinal error");
1325 }
1326 }
1327
1328 #[test]
1329 fn test_music_ordinal_display() {
1330 let ordinal = MusicOrdinals::TwentyFirst;
1331 assert_eq!(format!("{ordinal}"), "Twenty-first");
1332 }
1333
1334 #[test]
1335 fn test_static_arrays() {
1336 let musical_strings: Vec<_> = MUSICAL_ORDINAL_STRINGS.iter().cloned().collect();
1337 assert_eq!(musical_strings[0], "Zeroth");
1338 assert_eq!(musical_strings[1], "Unison");
1339 assert_eq!(musical_strings[8], "Octave");
1340 assert_eq!(musical_strings[15], "Double-octave");
1341 assert_eq!(musical_strings[22], "Triple-octave");
1342 }
1343}