1#![doc = include_str!("../DOCS.md")]
2
3pub mod units;
4
5pub struct DimensionalVariable {
6 pub value: f64,
7 pub unit: [f64; units::BASE_UNITS_SIZE],
8}
9
10impl DimensionalVariable {
12 pub fn new(value: f64, unit_str: &str) -> Result<Self, String> {
22 let (base_unit, conversion_factor) = unit_str_to_base_unit(unit_str)
24 .map_err(|e| format!("Failed to parse unit '{}': {}", unit_str, e))?;
25
26 return Ok(DimensionalVariable {
28 value: value * conversion_factor,
29 unit: base_unit,
30 });
31 }
32
33 pub fn value(&self) -> f64 {
35 self.value
36 }
37
38 pub fn value_in(&self, unit_str: &str) -> Result<f64, String> {
41
42 let (unit, conversion_factor) = unit_str_to_base_unit(unit_str)
43 .map_err(|e| format!("Failed to parse unit '{}': {}", unit_str, e))?;
44
45 if self.unit != unit {
47 return Err(format!("Incompatible unit conversion to: {}", unit_str));
48 }
49
50 return Ok(self.value / conversion_factor);
51 }
52
53 pub fn unit(&self) -> [f64; units::BASE_UNITS_SIZE] {
55 return self.unit;
56 }
57
58 pub fn is_unitless(&self) -> bool {
60 return self.unit.iter().all(|&e| e == 0.0);
61 }
62
63 pub fn try_add(&self, other: &DimensionalVariable) -> Result<DimensionalVariable, String> {
65 if self.unit != other.unit {
66 return Err("Incompatible units for addition".to_string());
67 }
68 return Ok(DimensionalVariable { value: self.value + other.value, unit: self.unit });
69 }
70
71 pub fn try_sub(&self, other: &DimensionalVariable) -> Result<DimensionalVariable, String> {
73 if self.unit != other.unit {
74 return Err("Incompatible units for subtraction".to_string());
75 }
76 return Ok(DimensionalVariable { value: self.value - other.value, unit: self.unit });
77 }
78
79 pub fn powi(&self, exp: i32) -> DimensionalVariable {
83 let mut unit = self.unit;
84 for i in 0..units::BASE_UNITS_SIZE { unit[i] *= exp as f64; }
85 return DimensionalVariable { value: self.value.powi(exp), unit };
86 }
87
88 pub fn powf(&self, exp: f64) -> Result<DimensionalVariable, String> {
91 let mut unit = self.unit;
92 for i in 0..units::BASE_UNITS_SIZE { unit[i] *= exp; }
93 return Ok(DimensionalVariable { value: self.value.powf(exp), unit });
94 }
95
96 pub fn sqrt(&self) -> Result<DimensionalVariable, String> {
99 if self.value < 0.0 {
100 return Err("sqrt of negative value".to_string());
101 }
102 return self.powf(0.5);
103 }
104
105 pub fn ln(&self) -> Result<f64, String> {
108 if !self.is_unitless() { return Err("ln requires a unitless quantity".to_string()); }
109 if self.value <= 0.0 { return Err("ln domain error (value <= 0)".to_string()); }
110 Ok(self.value.ln())
111 }
112
113 pub fn log2(&self) -> Result<f64, String> {
115 if !self.is_unitless() { return Err("log2 requires a unitless quantity".to_string()); }
116 if self.value <= 0.0 { return Err("log2 domain error (value <= 0)".to_string()); }
117 Ok(self.value.log2())
118 }
119
120 pub fn log10(&self) -> Result<f64, String> {
122 if !self.is_unitless() { return Err("log10 requires a unitless quantity".to_string()); }
123 if self.value <= 0.0 { return Err("log10 domain error (value <= 0)".to_string()); }
124 Ok(self.value.log10())
125 }
126
127 pub fn sin(&self) -> Result<f64, String> {
130 if !self.is_unitless() { return Err("sin requires a unitless quantity (radians)".to_string()); }
131 Ok(self.value.sin())
132 }
133
134 pub fn cos(&self) -> Result<f64, String> {
136 if !self.is_unitless() { return Err("cos requires a unitless quantity (radians)".to_string()); }
137 Ok(self.value.cos())
138 }
139
140 pub fn tan(&self) -> Result<f64, String> {
142 if !self.is_unitless() { return Err("tan requires a unitless quantity (radians)".to_string()); }
143 Ok(self.value.tan())
144 }
145
146 pub fn neg(&self) -> DimensionalVariable {
149 DimensionalVariable { value: -self.value, unit: self.unit }
150 }
151
152 pub fn abs(&self) -> DimensionalVariable {
154 DimensionalVariable { value: self.value.abs(), unit: self.unit }
155 }
156
157}
158
159fn unit_str_to_base_unit(units_str: &str) -> Result<([f64; units::BASE_UNITS_SIZE], f64), String> {
162
163 let cleaned_units_str = units_str.replace(['(', ')', '[', ']'], "");
165
166 let parts: Vec<&str> = cleaned_units_str.split('/').collect();
168 if parts.len() > 2 {
169 return Err("Unit string can only have one '/'".to_string());
170 }
171
172 let mut base_unit = [0.0; units::BASE_UNITS_SIZE];
173 let mut conversion_factor: f64 = 1.0;
174
175 for i in 0..parts.len() {
176 let denominator_multiplier = if i == 1 { -1 } else { 1 };
178
179 let units: Vec<&str> = {
181 let s = parts[i];
182 let mut out = Vec::new();
183 let mut start = 0usize;
184 let mut prev: Option<char> = None;
185 for (idx, ch) in s.char_indices() {
186 if ch == '-' && prev != Some('^') {
187 if idx > start {
188 out.push(&s[start..idx]);
189 }
190 start = idx + ch.len_utf8();
191 }
192 prev = Some(ch);
193 }
194 if start < s.len() {
195 out.push(&s[start..]);
196 }
197 out
198 };
199 for unit_str in units {
200
201 let (base, power) = read_unit_power(unit_str)?;
202
203 let unit_map = units::unit_map();
204 let unit = unit_map.get(base)
205 .ok_or_else(|| format!("Unknown unit: {}", base))?;
206
207 for j in 0..units::BASE_UNITS_SIZE {
208 base_unit[j] += unit.base_unit[j] * (power * denominator_multiplier) as f64;
209 }
210
211 conversion_factor *= unit.conversion_factor.powi(power * denominator_multiplier);
213 }
214 }
215
216 return Ok((base_unit, conversion_factor));
217}
218
219fn read_unit_power(unit: &str) -> Result<(&str, i32), String> {
223 let u = unit.trim();
224 if u.is_empty() {
225 return Err("Empty unit token".to_string());
226 }
227
228 let bytes = u.as_bytes();
229
230 let mut end = u.len();
232 while end > 0 && bytes[end - 1].is_ascii_digit() {
233 end -= 1;
234 }
235
236 if end == u.len() {
237 if end > 0 && bytes[end - 1] == b'^' {
239 return Err(format!("Missing exponent after '^' in \"{}\"", u));
240 }
241 return Ok((u, 1));
242 }
243
244 let mut start = end;
245 if start > 0 && (bytes[start - 1] == b'-') {
246 start -= 1;
247 }
248
249 let exp_str = &u[start..];
250 let exp: i32 = exp_str
251 .parse()
252 .map_err(|_| format!("Unable to read numeric power from \"{}\"", u))?;
253
254 let mut base_end = start;
256 if base_end > 0 && bytes[base_end - 1] == b'^' {
257 base_end -= 1;
258 }
259 let base = u[..base_end].trim();
260 if base.is_empty() {
261 return Err(format!("Missing unit symbol before exponent in \"{}\"", u));
262 }
263
264 Ok((base, exp))
265}
266
267fn add_unit_exponents(a: [f64; units::BASE_UNITS_SIZE], b: [f64; units::BASE_UNITS_SIZE]) -> [f64; units::BASE_UNITS_SIZE] {
270 let mut out = a;
271 for i in 0..units::BASE_UNITS_SIZE { out[i] += b[i]; }
272 out
273}
274
275fn sub_unit_exponents(a: [f64; units::BASE_UNITS_SIZE], b: [f64; units::BASE_UNITS_SIZE]) -> [f64; units::BASE_UNITS_SIZE] {
277 let mut out = a;
278 for i in 0..units::BASE_UNITS_SIZE { out[i] -= b[i]; }
279 out
280}
281
282use std::ops::{Add, Sub, Mul, Div, Neg, AddAssign, SubAssign, MulAssign, DivAssign};
284use std::cmp::Ordering;
285
286impl<'a, 'b> Add<&'b DimensionalVariable> for &'a DimensionalVariable {
288 type Output = DimensionalVariable;
289 fn add(self, rhs: &'b DimensionalVariable) -> Self::Output {
290 assert!(self.unit == rhs.unit, "Incompatible units for addition: {:?} vs {:?}", self.unit, rhs.unit);
291 DimensionalVariable { value: self.value + rhs.value, unit: self.unit }
292 }
293}
294
295impl Add<DimensionalVariable> for DimensionalVariable {
297 type Output = DimensionalVariable;
298 fn add(self, rhs: DimensionalVariable) -> Self::Output {
299 <&DimensionalVariable as Add<&DimensionalVariable>>::add(&self, &rhs)
300 }
301}
302
303impl<'b> Add<&'b DimensionalVariable> for DimensionalVariable {
304 type Output = DimensionalVariable;
305 fn add(self, rhs: &'b DimensionalVariable) -> Self::Output {
306 <&DimensionalVariable as Add<&DimensionalVariable>>::add(&self, rhs)
307 }
308}
309
310impl<'a> Add<DimensionalVariable> for &'a DimensionalVariable {
311 type Output = DimensionalVariable;
312 fn add(self, rhs: DimensionalVariable) -> Self::Output {
313 <&DimensionalVariable as Add<&DimensionalVariable>>::add(self, &rhs)
314 }
315}
316
317impl<'a, 'b> Sub<&'b DimensionalVariable> for &'a DimensionalVariable {
318 type Output = DimensionalVariable;
319 fn sub(self, rhs: &'b DimensionalVariable) -> Self::Output {
320 assert!(self.unit == rhs.unit, "Incompatible units for subtraction: {:?} vs {:?}", self.unit, rhs.unit);
321 DimensionalVariable { value: self.value - rhs.value, unit: self.unit }
322 }
323}
324
325impl Sub<DimensionalVariable> for DimensionalVariable {
326 type Output = DimensionalVariable;
327 fn sub(self, rhs: DimensionalVariable) -> Self::Output {
328 <&DimensionalVariable as Sub<&DimensionalVariable>>::sub(&self, &rhs)
329 }
330}
331
332impl<'b> Sub<&'b DimensionalVariable> for DimensionalVariable {
333 type Output = DimensionalVariable;
334 fn sub(self, rhs: &'b DimensionalVariable) -> Self::Output {
335 <&DimensionalVariable as Sub<&DimensionalVariable>>::sub(&self, rhs)
336 }
337}
338
339impl<'a> Sub<DimensionalVariable> for &'a DimensionalVariable {
340 type Output = DimensionalVariable;
341 fn sub(self, rhs: DimensionalVariable) -> Self::Output {
342 <&DimensionalVariable as Sub<&DimensionalVariable>>::sub(self, &rhs)
343 }
344}
345
346impl<'a, 'b> Mul<&'b DimensionalVariable> for &'a DimensionalVariable {
347 type Output = DimensionalVariable;
348 fn mul(self, rhs: &'b DimensionalVariable) -> Self::Output {
349 DimensionalVariable { value: self.value * rhs.value, unit: add_unit_exponents(self.unit, rhs.unit) }
350 }
351}
352
353impl Mul<DimensionalVariable> for DimensionalVariable {
354 type Output = DimensionalVariable;
355 fn mul(self, rhs: DimensionalVariable) -> Self::Output {
356 <&DimensionalVariable as Mul<&DimensionalVariable>>::mul(&self, &rhs)
357 }
358}
359
360impl<'b> Mul<&'b DimensionalVariable> for DimensionalVariable {
361 type Output = DimensionalVariable;
362 fn mul(self, rhs: &'b DimensionalVariable) -> Self::Output {
363 <&DimensionalVariable as Mul<&DimensionalVariable>>::mul(&self, rhs)
364 }
365}
366
367impl<'a> Mul<DimensionalVariable> for &'a DimensionalVariable {
368 type Output = DimensionalVariable;
369 fn mul(self, rhs: DimensionalVariable) -> Self::Output {
370 <&DimensionalVariable as Mul<&DimensionalVariable>>::mul(self, &rhs)
371 }
372}
373
374impl<'a, 'b> Div<&'b DimensionalVariable> for &'a DimensionalVariable {
375 type Output = DimensionalVariable;
376 fn div(self, rhs: &'b DimensionalVariable) -> Self::Output {
377 DimensionalVariable { value: self.value / rhs.value, unit: sub_unit_exponents(self.unit, rhs.unit) }
378 }
379}
380
381impl Div<DimensionalVariable> for DimensionalVariable {
382 type Output = DimensionalVariable;
383 fn div(self, rhs: DimensionalVariable) -> Self::Output {
384 <&DimensionalVariable as Div<&DimensionalVariable>>::div(&self, &rhs)
385 }
386}
387
388impl<'b> Div<&'b DimensionalVariable> for DimensionalVariable {
389 type Output = DimensionalVariable;
390 fn div(self, rhs: &'b DimensionalVariable) -> Self::Output {
391 <&DimensionalVariable as Div<&DimensionalVariable>>::div(&self, rhs)
392 }
393}
394
395impl<'a> Div<DimensionalVariable> for &'a DimensionalVariable {
396 type Output = DimensionalVariable;
397 fn div(self, rhs: DimensionalVariable) -> Self::Output {
398 <&DimensionalVariable as Div<&DimensionalVariable>>::div(self, &rhs)
399 }
400}
401
402impl AddAssign<&DimensionalVariable> for DimensionalVariable {
404 fn add_assign(&mut self, rhs: &DimensionalVariable) {
405 assert!(self.unit == rhs.unit, "Incompatible units for addition assignment: {:?} vs {:?}", self.unit, rhs.unit);
406 self.value += rhs.value;
407 }
408}
409
410impl SubAssign<&DimensionalVariable> for DimensionalVariable {
411 fn sub_assign(&mut self, rhs: &DimensionalVariable) {
412 assert!(self.unit == rhs.unit, "Incompatible units for subtraction assignment: {:?} vs {:?}", self.unit, rhs.unit);
413 self.value -= rhs.value;
414 }
415}
416
417impl MulAssign<&DimensionalVariable> for DimensionalVariable {
418 fn mul_assign(&mut self, rhs: &DimensionalVariable) {
419 self.value *= rhs.value;
420 self.unit = add_unit_exponents(self.unit, rhs.unit);
421 }
422}
423
424impl DivAssign<&DimensionalVariable> for DimensionalVariable {
425 fn div_assign(&mut self, rhs: &DimensionalVariable) {
426 self.value /= rhs.value;
427 self.unit = sub_unit_exponents(self.unit, rhs.unit);
428 }
429}
430
431impl<'a> Mul<f64> for &'a DimensionalVariable {
433 type Output = DimensionalVariable;
434 fn mul(self, rhs: f64) -> Self::Output {
435 DimensionalVariable { value: self.value * rhs, unit: self.unit }
436 }
437}
438
439impl Mul<f64> for DimensionalVariable {
440 type Output = DimensionalVariable;
441 fn mul(self, rhs: f64) -> Self::Output {
442 <&DimensionalVariable as Mul<f64>>::mul(&self, rhs)
443 }
444}
445
446impl MulAssign<f64> for DimensionalVariable {
447 fn mul_assign(&mut self, rhs: f64) {
448 self.value *= rhs;
449 }
450}
451
452impl<'a> Div<f64> for &'a DimensionalVariable {
453 type Output = DimensionalVariable;
454 fn div(self, rhs: f64) -> Self::Output {
455 DimensionalVariable { value: self.value / rhs, unit: self.unit }
456 }
457}
458
459impl Div<f64> for DimensionalVariable {
460 type Output = DimensionalVariable;
461 fn div(self, rhs: f64) -> Self::Output {
462 <&DimensionalVariable as Div<f64>>::div(&self, rhs)
463 }
464}
465
466impl DivAssign<f64> for DimensionalVariable {
467 fn div_assign(&mut self, rhs: f64) {
468 self.value /= rhs;
469 }
470}
471
472impl<'a> Mul<&'a DimensionalVariable> for f64 {
474 type Output = DimensionalVariable;
475 fn mul(self, rhs: &'a DimensionalVariable) -> Self::Output {
476 DimensionalVariable { value: self * rhs.value, unit: rhs.unit }
477 }
478}
479
480impl Mul<DimensionalVariable> for f64 {
481 type Output = DimensionalVariable;
482 fn mul(self, rhs: DimensionalVariable) -> Self::Output {
483 <f64 as Mul<&DimensionalVariable>>::mul(self, &rhs)
484 }
485}
486
487impl<'a> Div<&'a DimensionalVariable> for f64 {
488 type Output = DimensionalVariable;
489 fn div(self, rhs: &'a DimensionalVariable) -> Self::Output {
490 DimensionalVariable { value: self / rhs.value, unit: sub_unit_exponents([0.0; units::BASE_UNITS_SIZE], rhs.unit) }
491 }
492}
493
494impl Div<DimensionalVariable> for f64 {
495 type Output = DimensionalVariable;
496 fn div(self, rhs: DimensionalVariable) -> Self::Output {
497 <f64 as Div<&DimensionalVariable>>::div(self, &rhs)
498 }
499}
500
501impl<'a> Neg for &'a DimensionalVariable {
503 type Output = DimensionalVariable;
504 fn neg(self) -> Self::Output {
505 DimensionalVariable { value: -self.value, unit: self.unit }
506 }
507}
508
509impl Neg for DimensionalVariable {
510 type Output = DimensionalVariable;
511 fn neg(self) -> Self::Output {
512 <&DimensionalVariable as Neg>::neg(&self)
513 }
514}
515
516impl PartialEq for DimensionalVariable {
518 fn eq(&self, other: &Self) -> bool {
519 if self.unit != other.unit { return false; }
520 self.value == other.value
521 }
522}
523
524impl PartialOrd for DimensionalVariable {
525 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
526 if self.unit != other.unit { return None; }
527 self.value.partial_cmp(&other.value)
528 }
529}
530
531#[cfg(test)]
532mod tests {
533 use super::read_unit_power;
534
535 #[test]
536 fn read_unit_power_basic_cases() {
537 assert_eq!(read_unit_power("m").unwrap(), ("m", 1));
538 assert_eq!(read_unit_power("m3").unwrap(), ("m", 3));
539 assert_eq!(read_unit_power("m^3").unwrap(), ("m", 3));
540 assert_eq!(read_unit_power("m^-2").unwrap(), ("m", -2));
541 assert_eq!(read_unit_power("m-2").unwrap(), ("m", -2));
542 assert_eq!(read_unit_power(" kg^2 ").unwrap(), ("kg", 2));
543 assert_eq!(read_unit_power("undef").unwrap(), ("undef", 1)); }
545
546 #[test]
547 fn read_unit_power_errors() {
548 assert!(read_unit_power("").is_err());
549 let err = read_unit_power("m^").unwrap_err();
550 assert!(err.contains("Missing exponent"));
551 let err = read_unit_power("^2").unwrap_err();
552 assert!(err.contains("Missing unit symbol"));
553 }
554}