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.check_compatibility(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 check_compatibility(&self, other_unit: [f64; units::BASE_UNITS_SIZE]) -> bool {
54
55 if self.unit[7] == 1.0 {
57 return &self.unit[..units::BASE_UNITS_SIZE - 1] == &other_unit[..units::BASE_UNITS_SIZE - 1];
59 }
60
61 return self.unit == other_unit;
62 }
63
64 pub fn unit(&self) -> [f64; units::BASE_UNITS_SIZE] {
66 return self.unit;
67 }
68
69 pub fn is_unitless(&self) -> bool {
71 return self.unit.iter().all(|&e| e == 0.0);
72 }
73
74 pub fn try_add(&self, other: &DimensionalVariable) -> Result<DimensionalVariable, String> {
76 if !self.check_compatibility(other.unit) {
77 return Err("Incompatible units for addition".to_string());
78 }
79 return Ok(DimensionalVariable { value: self.value + other.value, unit: self.unit });
80 }
81
82 pub fn try_sub(&self, other: &DimensionalVariable) -> Result<DimensionalVariable, String> {
84 if !self.check_compatibility(other.unit) {
85 return Err("Incompatible units for subtraction".to_string());
86 }
87 return Ok(DimensionalVariable { value: self.value - other.value, unit: self.unit });
88 }
89
90 pub fn powi(&self, exp: i32) -> DimensionalVariable {
94 let mut unit = self.unit;
95 for i in 0..units::BASE_UNITS_SIZE { unit[i] *= exp as f64; }
96 return DimensionalVariable { value: self.value.powi(exp), unit };
97 }
98
99 pub fn powf(&self, exp: f64) -> Result<DimensionalVariable, String> {
102 let mut unit = self.unit;
103 for i in 0..units::BASE_UNITS_SIZE { unit[i] *= exp; }
104 return Ok(DimensionalVariable { value: self.value.powf(exp), unit });
105 }
106
107 pub fn sqrt(&self) -> Result<DimensionalVariable, String> {
110 if self.value < 0.0 {
111 return Err("sqrt of negative value".to_string());
112 }
113 return self.powf(0.5);
114 }
115
116 pub fn ln(&self) -> Result<f64, String> {
119 if !self.is_unitless() { return Err("ln requires a unitless quantity".to_string()); }
120 if self.value <= 0.0 { return Err("ln domain error (value <= 0)".to_string()); }
121 Ok(self.value.ln())
122 }
123
124 pub fn log2(&self) -> Result<f64, String> {
126 if !self.is_unitless() { return Err("log2 requires a unitless quantity".to_string()); }
127 if self.value <= 0.0 { return Err("log2 domain error (value <= 0)".to_string()); }
128 Ok(self.value.log2())
129 }
130
131 pub fn log10(&self) -> Result<f64, String> {
133 if !self.is_unitless() { return Err("log10 requires a unitless quantity".to_string()); }
134 if self.value <= 0.0 { return Err("log10 domain error (value <= 0)".to_string()); }
135 Ok(self.value.log10())
136 }
137
138 fn is_angle(&self) -> bool {
141 for i in 0..units::BASE_UNITS_SIZE - 1 {
143 if self.unit[i] != 0.0 {
144 return false;
145 }
146 }
147 self.unit[units::BASE_UNITS_SIZE - 1] == 1.0
148 }
149
150 pub fn sin(&self) -> Result<f64, String> {
152 if !self.is_angle() {
153 return Err("sin requires an angle quantity".to_string());
154 }
155 Ok(self.value.sin())
156 }
157
158 pub fn cos(&self) -> Result<f64, String> {
160 if !self.is_angle() {
161 return Err("cos requires an angle quantity".to_string());
162 }
163 Ok(self.value.cos())
164 }
165
166 pub fn tan(&self) -> Result<f64, String> {
168 if !self.is_angle() {
169 return Err("tan requires an angle quantity".to_string());
170 }
171 Ok(self.value.tan())
172 }
173
174 pub fn asin(&self) -> Result<DimensionalVariable, String> {
176 if !self.is_unitless() {
177 return Err("asin requires a unitless quantity".to_string());
178 }
179 if self.value < -1.0 || self.value > 1.0 {
180 return Err("asin requires a value in the range [-1, 1]".to_string());
181 }
182 let mut unit = [0.0; units::BASE_UNITS_SIZE];
183 unit[units::BASE_UNITS_SIZE - 1] = 1.0; Ok(DimensionalVariable { value: self.value.asin(), unit })
185 }
186
187 pub fn acos(&self) -> Result<DimensionalVariable, String> {
189 if !self.is_unitless() {
190 return Err("acos requires a unitless quantity".to_string());
191 }
192 if self.value < -1.0 || self.value > 1.0 {
193 return Err("acos requires a value in the range [-1, 1]".to_string());
194 }
195 let mut unit = [0.0; units::BASE_UNITS_SIZE];
196 unit[units::BASE_UNITS_SIZE - 1] = 1.0; Ok(DimensionalVariable { value: self.value.acos(), unit })
198 }
199
200 pub fn atan(&self) -> Result<DimensionalVariable, String> {
202 if !self.is_unitless() {
203 return Err("atan requires a unitless quantity".to_string());
204 }
205 let mut unit = [0.0; units::BASE_UNITS_SIZE];
206 unit[units::BASE_UNITS_SIZE - 1] = 1.0; Ok(DimensionalVariable { value: self.value.atan(), unit })
208 }
209
210 pub fn neg(&self) -> DimensionalVariable {
213 DimensionalVariable { value: -self.value, unit: self.unit }
214 }
215
216 pub fn abs(&self) -> DimensionalVariable {
218 DimensionalVariable { value: self.value.abs(), unit: self.unit }
219 }
220
221}
222
223impl std::fmt::Display for DimensionalVariable {
224 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
239 let mut numerator_parts: Vec<String> = Vec::new();
240 let mut denominator_parts: Vec<String> = Vec::new();
241
242 for (i, &exp) in self.unit.iter().enumerate() {
243 if exp == 0.0 {
244 continue;
245 }
246
247 let unit_symbol = units::BASE_UNITS[i];
248
249 if exp > 0.0 {
250 if exp == 1.0 {
251 numerator_parts.push(unit_symbol.to_string());
252 } else if exp == exp.trunc() {
253 numerator_parts.push(format!("{}^{}", unit_symbol, exp as i32));
255 } else {
256 numerator_parts.push(format!("{}^{}", unit_symbol, exp));
258 }
259 } else {
260 let abs_exp = exp.abs();
261 if abs_exp == 1.0 {
262 denominator_parts.push(unit_symbol.to_string());
263 } else if abs_exp == abs_exp.trunc() {
264 denominator_parts.push(format!("{}^{}", unit_symbol, abs_exp as i32));
266 } else {
267 denominator_parts.push(format!("{}^{}", unit_symbol, abs_exp));
269 }
270 }
271 }
272
273 let unit_str = if numerator_parts.is_empty() && denominator_parts.is_empty() {
274 "(unitless)".to_string()
275 } else if denominator_parts.is_empty() {
276 numerator_parts.join("*")
277 } else if numerator_parts.is_empty() {
278 format!("1/{}", denominator_parts.join("*"))
279 } else {
280 format!("{}/{}", numerator_parts.join("*"), denominator_parts.join("*"))
281 };
282
283 write!(f, "{} {}", self.value, unit_str)
284 }
285}
286
287pub fn asin(x: f64) -> Result<DimensionalVariable, String> {
289 return DimensionalVariable::new(x, "").unwrap().asin();
290}
291
292pub fn acos(x: f64) -> Result<DimensionalVariable, String> {
294 return DimensionalVariable::new(x, "").unwrap().acos();
295}
296
297pub fn atan(x: f64) -> Result<DimensionalVariable, String> {
299 return DimensionalVariable::new(x, "").unwrap().atan();
300}
301
302fn unit_str_to_base_unit(units_str: &str) -> Result<([f64; units::BASE_UNITS_SIZE], f64), String> {
305
306 let cleaned_units_str = units_str.replace(['(', ')', '[', ']'], "");
308
309 let parts: Vec<&str> = cleaned_units_str.split('/').collect();
311 if parts.len() > 2 {
312 return Err("Unit string can only have one '/'".to_string());
313 }
314
315 let mut base_unit = [0.0; units::BASE_UNITS_SIZE];
316 let mut conversion_factor: f64 = 1.0;
317
318 for i in 0..parts.len() {
319 let denominator_multiplier = if i == 1 { -1 } else { 1 };
321
322 let units: Vec<&str> = {
324 let s = parts[i];
325 let mut out = Vec::new();
326 let mut start = 0usize;
327 let mut prev: Option<char> = None;
328 for (idx, ch) in s.char_indices() {
329 if ch == '-' && prev != Some('^') {
330 if idx > start {
331 out.push(&s[start..idx]);
332 }
333 start = idx + ch.len_utf8();
334 }
335 prev = Some(ch);
336 }
337 if start < s.len() {
338 out.push(&s[start..]);
339 }
340 out
341 };
342 for unit_str in units {
343
344 let (base, power) = read_unit_power(unit_str)?;
345
346 let unit_map = units::unit_map();
347 let unit = unit_map.get(base)
348 .ok_or_else(|| format!("Unknown unit: {}", base))?;
349
350 for j in 0..units::BASE_UNITS_SIZE {
351 base_unit[j] += unit.base_unit[j] * (power * denominator_multiplier) as f64;
352 }
353
354 conversion_factor *= unit.conversion_factor.powi(power * denominator_multiplier);
356 }
357 }
358
359 return Ok((base_unit, conversion_factor));
360}
361
362fn read_unit_power(unit: &str) -> Result<(&str, i32), String> {
366 let u = unit.trim();
367 if u.is_empty() {
368 return Err("Empty unit token".to_string());
369 }
370
371 let bytes = u.as_bytes();
372
373 let mut end = u.len();
375 while end > 0 && bytes[end - 1].is_ascii_digit() {
376 end -= 1;
377 }
378
379 if end == u.len() {
380 if end > 0 && bytes[end - 1] == b'^' {
382 return Err(format!("Missing exponent after '^' in \"{}\"", u));
383 }
384 return Ok((u, 1));
385 }
386
387 let mut start = end;
388 if start > 0 && (bytes[start - 1] == b'-') {
389 start -= 1;
390 }
391
392 let exp_str = &u[start..];
393 let exp: i32 = exp_str
394 .parse()
395 .map_err(|_| format!("Unable to read numeric power from \"{}\"", u))?;
396
397 let mut base_end = start;
399 if base_end > 0 && bytes[base_end - 1] == b'^' {
400 base_end -= 1;
401 }
402 let base = u[..base_end].trim();
403 if base.is_empty() {
404 return Err(format!("Missing unit symbol before exponent in \"{}\"", u));
405 }
406
407 Ok((base, exp))
408}
409
410fn add_unit_exponents(a: [f64; units::BASE_UNITS_SIZE], b: [f64; units::BASE_UNITS_SIZE]) -> [f64; units::BASE_UNITS_SIZE] {
413 let mut out = a;
414 for i in 0..units::BASE_UNITS_SIZE { out[i] += b[i]; }
415 out
416}
417
418fn sub_unit_exponents(a: [f64; units::BASE_UNITS_SIZE], b: [f64; units::BASE_UNITS_SIZE]) -> [f64; units::BASE_UNITS_SIZE] {
420 let mut out = a;
421 for i in 0..units::BASE_UNITS_SIZE { out[i] -= b[i]; }
422 out
423}
424
425use std::ops::{Add, Sub, Mul, Div, Neg, AddAssign, SubAssign, MulAssign, DivAssign};
427use std::cmp::Ordering;
428
429impl<'a, 'b> Add<&'b DimensionalVariable> for &'a DimensionalVariable {
431 type Output = DimensionalVariable;
432 fn add(self, rhs: &'b DimensionalVariable) -> Self::Output {
433 return self.try_add(rhs).expect("Incompatible units for addition");
434 }
435}
436
437impl Add<DimensionalVariable> for DimensionalVariable {
439 type Output = DimensionalVariable;
440 fn add(self, rhs: DimensionalVariable) -> Self::Output {
441 <&DimensionalVariable as Add<&DimensionalVariable>>::add(&self, &rhs)
442 }
443}
444
445impl<'b> Add<&'b DimensionalVariable> for DimensionalVariable {
446 type Output = DimensionalVariable;
447 fn add(self, rhs: &'b DimensionalVariable) -> Self::Output {
448 <&DimensionalVariable as Add<&DimensionalVariable>>::add(&self, rhs)
449 }
450}
451
452impl<'a> Add<DimensionalVariable> for &'a DimensionalVariable {
453 type Output = DimensionalVariable;
454 fn add(self, rhs: DimensionalVariable) -> Self::Output {
455 <&DimensionalVariable as Add<&DimensionalVariable>>::add(self, &rhs)
456 }
457}
458
459impl<'a, 'b> Sub<&'b DimensionalVariable> for &'a DimensionalVariable {
460 type Output = DimensionalVariable;
461 fn sub(self, rhs: &'b DimensionalVariable) -> Self::Output {
462 return self.try_sub(rhs).expect("Incompatible units for subtraction");
463 }
464}
465
466impl Sub<DimensionalVariable> for DimensionalVariable {
467 type Output = DimensionalVariable;
468 fn sub(self, rhs: DimensionalVariable) -> Self::Output {
469 <&DimensionalVariable as Sub<&DimensionalVariable>>::sub(&self, &rhs)
470 }
471}
472
473impl<'b> Sub<&'b DimensionalVariable> for DimensionalVariable {
474 type Output = DimensionalVariable;
475 fn sub(self, rhs: &'b DimensionalVariable) -> Self::Output {
476 <&DimensionalVariable as Sub<&DimensionalVariable>>::sub(&self, rhs)
477 }
478}
479
480impl<'a> Sub<DimensionalVariable> for &'a DimensionalVariable {
481 type Output = DimensionalVariable;
482 fn sub(self, rhs: DimensionalVariable) -> Self::Output {
483 <&DimensionalVariable as Sub<&DimensionalVariable>>::sub(self, &rhs)
484 }
485}
486
487impl<'a, 'b> Mul<&'b DimensionalVariable> for &'a DimensionalVariable {
488 type Output = DimensionalVariable;
489 fn mul(self, rhs: &'b DimensionalVariable) -> Self::Output {
490 DimensionalVariable { value: self.value * rhs.value, unit: add_unit_exponents(self.unit, rhs.unit) }
491 }
492}
493
494impl Mul<DimensionalVariable> for DimensionalVariable {
495 type Output = DimensionalVariable;
496 fn mul(self, rhs: DimensionalVariable) -> Self::Output {
497 <&DimensionalVariable as Mul<&DimensionalVariable>>::mul(&self, &rhs)
498 }
499}
500
501impl<'b> Mul<&'b DimensionalVariable> for DimensionalVariable {
502 type Output = DimensionalVariable;
503 fn mul(self, rhs: &'b DimensionalVariable) -> Self::Output {
504 <&DimensionalVariable as Mul<&DimensionalVariable>>::mul(&self, rhs)
505 }
506}
507
508impl<'a> Mul<DimensionalVariable> for &'a DimensionalVariable {
509 type Output = DimensionalVariable;
510 fn mul(self, rhs: DimensionalVariable) -> Self::Output {
511 <&DimensionalVariable as Mul<&DimensionalVariable>>::mul(self, &rhs)
512 }
513}
514
515impl<'a, 'b> Div<&'b DimensionalVariable> for &'a DimensionalVariable {
516 type Output = DimensionalVariable;
517 fn div(self, rhs: &'b DimensionalVariable) -> Self::Output {
518 DimensionalVariable { value: self.value / rhs.value, unit: sub_unit_exponents(self.unit, rhs.unit) }
519 }
520}
521
522impl Div<DimensionalVariable> for DimensionalVariable {
523 type Output = DimensionalVariable;
524 fn div(self, rhs: DimensionalVariable) -> Self::Output {
525 <&DimensionalVariable as Div<&DimensionalVariable>>::div(&self, &rhs)
526 }
527}
528
529impl<'b> Div<&'b DimensionalVariable> for DimensionalVariable {
530 type Output = DimensionalVariable;
531 fn div(self, rhs: &'b DimensionalVariable) -> Self::Output {
532 <&DimensionalVariable as Div<&DimensionalVariable>>::div(&self, rhs)
533 }
534}
535
536impl<'a> Div<DimensionalVariable> for &'a DimensionalVariable {
537 type Output = DimensionalVariable;
538 fn div(self, rhs: DimensionalVariable) -> Self::Output {
539 <&DimensionalVariable as Div<&DimensionalVariable>>::div(self, &rhs)
540 }
541}
542
543impl AddAssign<&DimensionalVariable> for DimensionalVariable {
545 fn add_assign(&mut self, rhs: &DimensionalVariable) {
546 *self = self.try_add(rhs).expect("Incompatible units for addition assignment");
547 }
548}
549
550impl SubAssign<&DimensionalVariable> for DimensionalVariable {
551 fn sub_assign(&mut self, rhs: &DimensionalVariable) {
552 *self = self.try_sub(rhs).expect("Incompatible units for subtraction assignment");
553 }
554}
555
556impl MulAssign<&DimensionalVariable> for DimensionalVariable {
557 fn mul_assign(&mut self, rhs: &DimensionalVariable) {
558 self.value *= rhs.value;
559 self.unit = add_unit_exponents(self.unit, rhs.unit);
560 }
561}
562
563impl DivAssign<&DimensionalVariable> for DimensionalVariable {
564 fn div_assign(&mut self, rhs: &DimensionalVariable) {
565 self.value /= rhs.value;
566 self.unit = sub_unit_exponents(self.unit, rhs.unit);
567 }
568}
569
570impl<'a> Mul<f64> for &'a DimensionalVariable {
572 type Output = DimensionalVariable;
573 fn mul(self, rhs: f64) -> Self::Output {
574 DimensionalVariable { value: self.value * rhs, unit: self.unit }
575 }
576}
577
578impl Mul<f64> for DimensionalVariable {
579 type Output = DimensionalVariable;
580 fn mul(self, rhs: f64) -> Self::Output {
581 <&DimensionalVariable as Mul<f64>>::mul(&self, rhs)
582 }
583}
584
585impl MulAssign<f64> for DimensionalVariable {
586 fn mul_assign(&mut self, rhs: f64) {
587 self.value *= rhs;
588 }
589}
590
591impl<'a> Div<f64> for &'a DimensionalVariable {
592 type Output = DimensionalVariable;
593 fn div(self, rhs: f64) -> Self::Output {
594 DimensionalVariable { value: self.value / rhs, unit: self.unit }
595 }
596}
597
598impl Div<f64> for DimensionalVariable {
599 type Output = DimensionalVariable;
600 fn div(self, rhs: f64) -> Self::Output {
601 <&DimensionalVariable as Div<f64>>::div(&self, rhs)
602 }
603}
604
605impl DivAssign<f64> for DimensionalVariable {
606 fn div_assign(&mut self, rhs: f64) {
607 self.value /= rhs;
608 }
609}
610
611impl<'a> Mul<&'a DimensionalVariable> for f64 {
613 type Output = DimensionalVariable;
614 fn mul(self, rhs: &'a DimensionalVariable) -> Self::Output {
615 DimensionalVariable { value: self * rhs.value, unit: rhs.unit }
616 }
617}
618
619impl Mul<DimensionalVariable> for f64 {
620 type Output = DimensionalVariable;
621 fn mul(self, rhs: DimensionalVariable) -> Self::Output {
622 <f64 as Mul<&DimensionalVariable>>::mul(self, &rhs)
623 }
624}
625
626impl<'a> Div<&'a DimensionalVariable> for f64 {
627 type Output = DimensionalVariable;
628 fn div(self, rhs: &'a DimensionalVariable) -> Self::Output {
629 DimensionalVariable { value: self / rhs.value, unit: sub_unit_exponents([0.0; units::BASE_UNITS_SIZE], rhs.unit) }
630 }
631}
632
633impl Div<DimensionalVariable> for f64 {
634 type Output = DimensionalVariable;
635 fn div(self, rhs: DimensionalVariable) -> Self::Output {
636 <f64 as Div<&DimensionalVariable>>::div(self, &rhs)
637 }
638}
639
640impl<'a> Neg for &'a DimensionalVariable {
642 type Output = DimensionalVariable;
643 fn neg(self) -> Self::Output {
644 DimensionalVariable { value: -self.value, unit: self.unit }
645 }
646}
647
648impl Neg for DimensionalVariable {
649 type Output = DimensionalVariable;
650 fn neg(self) -> Self::Output {
651 <&DimensionalVariable as Neg>::neg(&self)
652 }
653}
654
655impl PartialEq for DimensionalVariable {
657 fn eq(&self, other: &Self) -> bool {
658 if !self.check_compatibility(other.unit) {
659 return false;
660 }
661 self.value == other.value
662 }
663}
664
665impl PartialOrd for DimensionalVariable {
666 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
667 if !self.check_compatibility(other.unit) { return None; }
668 self.value.partial_cmp(&other.value)
669 }
670}
671
672#[cfg(test)]
673mod tests {
674 use super::read_unit_power;
675
676 #[test]
677 fn read_unit_power_basic_cases() {
678 assert_eq!(read_unit_power("m").unwrap(), ("m", 1));
679 assert_eq!(read_unit_power("m3").unwrap(), ("m", 3));
680 assert_eq!(read_unit_power("m^3").unwrap(), ("m", 3));
681 assert_eq!(read_unit_power("m^-2").unwrap(), ("m", -2));
682 assert_eq!(read_unit_power("m-2").unwrap(), ("m", -2));
683 assert_eq!(read_unit_power(" kg^2 ").unwrap(), ("kg", 2));
684 assert_eq!(read_unit_power("undef").unwrap(), ("undef", 1)); }
686
687 #[test]
688 fn read_unit_power_errors() {
689 assert!(read_unit_power("").is_err());
690 let err = read_unit_power("m^").unwrap_err();
691 assert!(err.contains("Missing exponent"));
692 let err = read_unit_power("^2").unwrap_err();
693 assert!(err.contains("Missing unit symbol"));
694 }
695}