mathypad_core/units/
value.rs1use super::types::{Unit, UnitType};
4use crate::{FLOAT_EPSILON, MAX_INTEGER_FOR_FORMATTING};
5
6#[derive(Debug, Clone)]
8pub struct UnitValue {
9 pub value: f64,
10 pub unit: Option<Unit>,
11}
12
13impl UnitValue {
14 pub fn new(value: f64, unit: Option<Unit>) -> Self {
16 UnitValue { value, unit }
17 }
18
19 pub fn to_unit(&self, target_unit: &Unit) -> Option<UnitValue> {
21 match &self.unit {
22 Some(current_unit) => {
23 if let (
25 Unit::RateUnit(curr_num, curr_denom),
26 Unit::RateUnit(targ_num, targ_denom),
27 ) = (current_unit, target_unit)
28 {
29 if curr_num.unit_type() == targ_num.unit_type() && curr_num == targ_num {
31 let curr_denom_base = curr_denom.to_base_value(1.0);
33 let targ_denom_base = targ_denom.to_base_value(1.0);
34
35 let conversion_factor = targ_denom_base / curr_denom_base;
39 let converted_value = self.value * conversion_factor;
40
41 return Some(UnitValue::new(converted_value, Some(target_unit.clone())));
42 } else if curr_num.unit_type() == UnitType::Currency
43 && targ_num.unit_type() == UnitType::Currency
44 {
45 return None;
47 }
48 }
49
50 if current_unit.unit_type() == target_unit.unit_type()
52 || self.can_convert_between_data_rates(current_unit, target_unit)
53 {
54 let base_value = current_unit.to_base_value(self.value);
55 let converted_value = target_unit.clone().from_base_value(base_value);
56 Some(UnitValue::new(converted_value, Some(target_unit.clone())))
57 }
58 else if self.can_convert_between_bits_bytes(current_unit, target_unit) {
60 self.convert_bits_bytes(current_unit, target_unit)
61 } else {
62 None }
64 }
65 None => {
66 use super::types::UnitType;
68 if target_unit.unit_type() == UnitType::Percentage {
69 let converted_value = target_unit.clone().from_base_value(self.value);
70 Some(UnitValue::new(converted_value, Some(target_unit.clone())))
71 } else {
72 None }
74 }
75 }
76 }
77
78 fn can_convert_between_data_rates(&self, current: &Unit, target: &Unit) -> bool {
80 use super::types::UnitType;
81 matches!(
82 (current.unit_type(), target.unit_type()),
83 (UnitType::DataRate { .. }, UnitType::DataRate { .. })
84 )
85 }
86
87 fn can_convert_between_bits_bytes(&self, current: &Unit, target: &Unit) -> bool {
89 use super::types::UnitType;
90 matches!(
91 (current.unit_type(), target.unit_type()),
92 (UnitType::Bit, UnitType::Data)
93 | (UnitType::Data, UnitType::Bit)
94 | (UnitType::BitRate, UnitType::DataRate { .. })
95 | (UnitType::DataRate { .. }, UnitType::BitRate)
96 )
97 }
98
99 fn convert_bits_bytes(&self, current: &Unit, target: &Unit) -> Option<UnitValue> {
101 use super::types::UnitType;
102
103 match (current.unit_type(), target.unit_type()) {
104 (UnitType::Bit, UnitType::Data) => {
106 let bits = current.to_base_value(self.value); let bytes = bits / 8.0; let converted_value = target.clone().from_base_value(bytes);
109 Some(UnitValue::new(converted_value, Some(target.clone())))
110 }
111 (UnitType::Data, UnitType::Bit) => {
113 let bytes = current.to_base_value(self.value); let bits = bytes * 8.0; let converted_value = target.clone().from_base_value(bits);
116 Some(UnitValue::new(converted_value, Some(target.clone())))
117 }
118 (UnitType::BitRate, UnitType::DataRate { .. }) => {
120 let bits_per_sec = current.to_base_value(self.value); let bytes_per_sec = bits_per_sec / 8.0; let converted_value = target.clone().from_base_value(bytes_per_sec);
123 Some(UnitValue::new(converted_value, Some(target.clone())))
124 }
125 (UnitType::DataRate { .. }, UnitType::BitRate) => {
127 let bytes_per_sec = current.to_base_value(self.value); let bits_per_sec = bytes_per_sec * 8.0; let converted_value = target.clone().from_base_value(bits_per_sec);
130 Some(UnitValue::new(converted_value, Some(target.clone())))
131 }
132 _ => None,
133 }
134 }
135
136 pub fn format(&self) -> String {
138 let formatted_value =
139 if self.value.fract() == 0.0 && self.value.abs() < MAX_INTEGER_FOR_FORMATTING {
140 format_number_with_commas(self.value as i64)
141 } else {
142 format_decimal_with_commas(self.value)
143 };
144
145 match &self.unit {
146 Some(unit) => format!("{} {}", formatted_value, unit.display_name()),
147 None => formatted_value,
148 }
149 }
150}
151
152fn format_number_with_commas(num: i64) -> String {
154 let num_str = num.to_string();
155 let mut result = String::new();
156 let chars: Vec<char> = num_str.chars().collect();
157
158 let is_negative = chars.first() == Some(&'-');
159 let start_idx = if is_negative { 1 } else { 0 };
160
161 if is_negative {
162 result.push('-');
163 }
164
165 for (i, ch) in chars[start_idx..].iter().enumerate() {
166 if i > 0 && (chars.len() - start_idx - i) % 3 == 0 {
167 result.push(',');
168 }
169 result.push(*ch);
170 }
171
172 result
173}
174
175fn format_decimal_with_commas(num: f64) -> String {
177 if num.abs() < FLOAT_EPSILON {
178 return "0".to_string();
179 }
180
181 let is_negative = num < 0.0;
182 let abs_num = num.abs();
183
184 let formatted = format!("{:.3}", abs_num);
185
186 let parts: Vec<&str> = formatted.split('.').collect();
188 if parts.len() != 2 {
189 return if is_negative {
190 format!("-{}", formatted)
191 } else {
192 formatted
193 };
194 }
195
196 let whole_part = parts[0];
197 let decimal_part = parts[1];
198
199 let whole_with_commas = if whole_part == "0" {
201 "0".to_string()
202 } else {
203 let whole_chars: Vec<char> = whole_part.chars().collect();
204 let mut result = String::new();
205
206 for (i, ch) in whole_chars.iter().enumerate() {
207 if i > 0 && (whole_chars.len() - i) % 3 == 0 {
208 result.push(',');
209 }
210 result.push(*ch);
211 }
212 result
213 };
214
215 let decimal_trimmed = decimal_part.trim_end_matches('0');
217
218 let formatted_result = if decimal_trimmed.is_empty() {
219 whole_with_commas
220 } else {
221 format!("{}.{}", whole_with_commas, decimal_trimmed)
222 };
223
224 if is_negative {
225 format!("-{}", formatted_result)
226 } else {
227 formatted_result
228 }
229}