1use std::cmp::Ordering;
8
9pub fn sign(coef: &str) -> i32 {
11 let (neg, mag) = split_sign(coef);
12 if mag.bytes().all(|b| b == b'0') {
13 0
14 } else if neg {
15 -1
16 } else {
17 1
18 }
19}
20
21fn split_sign(coef: &str) -> (bool, &str) {
22 match coef.strip_prefix('-') {
23 Some(rest) => (true, rest),
24 None => (false, coef),
25 }
26}
27
28pub fn render(coef: &str, scale: u32) -> String {
31 let (neg, mag) = split_sign(coef);
32 let scale = scale as usize;
33 let mag = if mag.len() <= scale {
34 format!("{}{mag}", "0".repeat(scale + 1 - mag.len()))
35 } else {
36 mag.to_string()
37 };
38 let (int, frac) = mag.split_at(mag.len() - scale);
39 let sign = if neg { "-" } else { "" };
40 if scale == 0 {
41 format!("{sign}{int}")
42 } else {
43 format!("{sign}{int}.{frac}")
44 }
45}
46
47pub fn canonical(coef: &str, scale: u32) -> String {
50 let (neg, mag) = split_sign(coef);
51 let (mut mag, mut scale) = (mag.trim_start_matches('0').to_string(), scale);
52 while scale > 0 && mag.ends_with('0') {
53 mag.pop();
54 scale -= 1;
55 }
56 if mag.is_empty() {
57 return "0".to_string();
58 }
59 render(&format!("{}{mag}", if neg { "-" } else { "" }), scale)
60}
61
62pub fn cmp(a_coef: &str, a_scale: u32, b_coef: &str, b_scale: u32) -> Ordering {
65 let (sa, sb) = (sign(a_coef), sign(b_coef));
66 if sa != sb {
67 return sa.cmp(&sb);
68 }
69 if sa == 0 {
70 return Ordering::Equal;
71 }
72 let (_, ma) = split_sign(a_coef);
73 let (_, mb) = split_sign(b_coef);
74 let scale = a_scale.max(b_scale);
75 let pad = |m: &str, s: u32| {
76 let mut out = m.trim_start_matches('0').to_string();
77 out.push_str(&"0".repeat((scale - s) as usize));
78 out
79 };
80 let (pa, pb) = (pad(ma, a_scale), pad(mb, b_scale));
81 let mag = pa.len().cmp(&pb.len()).then_with(|| pa.cmp(&pb));
82 if sa < 0 { mag.reverse() } else { mag }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn orders_by_value_not_scale() {
91 assert_eq!(cmp("120", 2, "12", 1), Ordering::Equal);
92 assert_eq!(cmp("1", 0, "99", 2), Ordering::Greater);
93 assert_eq!(cmp("-5", 0, "-50", 1), Ordering::Equal);
94 assert_eq!(cmp("-5", 0, "-6", 0), Ordering::Greater);
95 assert_eq!(cmp("-1", 0, "1", 0), Ordering::Less);
96 assert_eq!(cmp("0", 0, "-0", 3), Ordering::Equal);
97 }
98
99 #[test]
100 fn renders() {
101 assert_eq!(render("120", 2), "1.20");
102 assert_eq!(render("-5", 3), "-0.005");
103 assert_eq!(render("7", 0), "7");
104 assert_eq!(render("0", 2), "0.00");
105 }
106
107 #[test]
108 fn canonicalises() {
109 assert_eq!(canonical("120", 2), "1.2");
110 assert_eq!(canonical("100", 2), "1");
111 assert_eq!(canonical("-000", 1), "0");
112 assert_eq!(canonical("5", 3), "0.005");
113 }
114
115 #[test]
116 fn signs() {
117 assert_eq!(sign("000"), 0);
118 assert_eq!(sign("-0"), 0);
119 assert_eq!(sign("-12"), -1);
120 assert_eq!(sign("12"), 1);
121 }
122}