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
223pub fn asin(x: f64) -> Result<DimensionalVariable, String> {
225 return DimensionalVariable::new(x, "").unwrap().asin();
226}
227
228pub fn acos(x: f64) -> Result<DimensionalVariable, String> {
230 return DimensionalVariable::new(x, "").unwrap().acos();
231}
232
233pub fn atan(x: f64) -> Result<DimensionalVariable, String> {
235 return DimensionalVariable::new(x, "").unwrap().atan();
236}
237
238fn unit_str_to_base_unit(units_str: &str) -> Result<([f64; units::BASE_UNITS_SIZE], f64), String> {
241
242 let cleaned_units_str = units_str.replace(['(', ')', '[', ']'], "");
244
245 let parts: Vec<&str> = cleaned_units_str.split('/').collect();
247 if parts.len() > 2 {
248 return Err("Unit string can only have one '/'".to_string());
249 }
250
251 let mut base_unit = [0.0; units::BASE_UNITS_SIZE];
252 let mut conversion_factor: f64 = 1.0;
253
254 for i in 0..parts.len() {
255 let denominator_multiplier = if i == 1 { -1 } else { 1 };
257
258 let units: Vec<&str> = {
260 let s = parts[i];
261 let mut out = Vec::new();
262 let mut start = 0usize;
263 let mut prev: Option<char> = None;
264 for (idx, ch) in s.char_indices() {
265 if ch == '-' && prev != Some('^') {
266 if idx > start {
267 out.push(&s[start..idx]);
268 }
269 start = idx + ch.len_utf8();
270 }
271 prev = Some(ch);
272 }
273 if start < s.len() {
274 out.push(&s[start..]);
275 }
276 out
277 };
278 for unit_str in units {
279
280 let (base, power) = read_unit_power(unit_str)?;
281
282 let unit_map = units::unit_map();
283 let unit = unit_map.get(base)
284 .ok_or_else(|| format!("Unknown unit: {}", base))?;
285
286 for j in 0..units::BASE_UNITS_SIZE {
287 base_unit[j] += unit.base_unit[j] * (power * denominator_multiplier) as f64;
288 }
289
290 conversion_factor *= unit.conversion_factor.powi(power * denominator_multiplier);
292 }
293 }
294
295 return Ok((base_unit, conversion_factor));
296}
297
298fn read_unit_power(unit: &str) -> Result<(&str, i32), String> {
302 let u = unit.trim();
303 if u.is_empty() {
304 return Err("Empty unit token".to_string());
305 }
306
307 let bytes = u.as_bytes();
308
309 let mut end = u.len();
311 while end > 0 && bytes[end - 1].is_ascii_digit() {
312 end -= 1;
313 }
314
315 if end == u.len() {
316 if end > 0 && bytes[end - 1] == b'^' {
318 return Err(format!("Missing exponent after '^' in \"{}\"", u));
319 }
320 return Ok((u, 1));
321 }
322
323 let mut start = end;
324 if start > 0 && (bytes[start - 1] == b'-') {
325 start -= 1;
326 }
327
328 let exp_str = &u[start..];
329 let exp: i32 = exp_str
330 .parse()
331 .map_err(|_| format!("Unable to read numeric power from \"{}\"", u))?;
332
333 let mut base_end = start;
335 if base_end > 0 && bytes[base_end - 1] == b'^' {
336 base_end -= 1;
337 }
338 let base = u[..base_end].trim();
339 if base.is_empty() {
340 return Err(format!("Missing unit symbol before exponent in \"{}\"", u));
341 }
342
343 Ok((base, exp))
344}
345
346fn add_unit_exponents(a: [f64; units::BASE_UNITS_SIZE], b: [f64; units::BASE_UNITS_SIZE]) -> [f64; units::BASE_UNITS_SIZE] {
349 let mut out = a;
350 for i in 0..units::BASE_UNITS_SIZE { out[i] += b[i]; }
351 out
352}
353
354fn sub_unit_exponents(a: [f64; units::BASE_UNITS_SIZE], b: [f64; units::BASE_UNITS_SIZE]) -> [f64; units::BASE_UNITS_SIZE] {
356 let mut out = a;
357 for i in 0..units::BASE_UNITS_SIZE { out[i] -= b[i]; }
358 out
359}
360
361use std::ops::{Add, Sub, Mul, Div, Neg, AddAssign, SubAssign, MulAssign, DivAssign};
363use std::cmp::Ordering;
364
365impl<'a, 'b> Add<&'b DimensionalVariable> for &'a DimensionalVariable {
367 type Output = DimensionalVariable;
368 fn add(self, rhs: &'b DimensionalVariable) -> Self::Output {
369 return self.try_add(rhs).expect("Incompatible units for addition");
370 }
371}
372
373impl Add<DimensionalVariable> for DimensionalVariable {
375 type Output = DimensionalVariable;
376 fn add(self, rhs: DimensionalVariable) -> Self::Output {
377 <&DimensionalVariable as Add<&DimensionalVariable>>::add(&self, &rhs)
378 }
379}
380
381impl<'b> Add<&'b DimensionalVariable> for DimensionalVariable {
382 type Output = DimensionalVariable;
383 fn add(self, rhs: &'b DimensionalVariable) -> Self::Output {
384 <&DimensionalVariable as Add<&DimensionalVariable>>::add(&self, rhs)
385 }
386}
387
388impl<'a> Add<DimensionalVariable> for &'a DimensionalVariable {
389 type Output = DimensionalVariable;
390 fn add(self, rhs: DimensionalVariable) -> Self::Output {
391 <&DimensionalVariable as Add<&DimensionalVariable>>::add(self, &rhs)
392 }
393}
394
395impl<'a, 'b> Sub<&'b DimensionalVariable> for &'a DimensionalVariable {
396 type Output = DimensionalVariable;
397 fn sub(self, rhs: &'b DimensionalVariable) -> Self::Output {
398 return self.try_sub(rhs).expect("Incompatible units for subtraction");
399 }
400}
401
402impl Sub<DimensionalVariable> for DimensionalVariable {
403 type Output = DimensionalVariable;
404 fn sub(self, rhs: DimensionalVariable) -> Self::Output {
405 <&DimensionalVariable as Sub<&DimensionalVariable>>::sub(&self, &rhs)
406 }
407}
408
409impl<'b> Sub<&'b DimensionalVariable> for DimensionalVariable {
410 type Output = DimensionalVariable;
411 fn sub(self, rhs: &'b DimensionalVariable) -> Self::Output {
412 <&DimensionalVariable as Sub<&DimensionalVariable>>::sub(&self, rhs)
413 }
414}
415
416impl<'a> Sub<DimensionalVariable> for &'a DimensionalVariable {
417 type Output = DimensionalVariable;
418 fn sub(self, rhs: DimensionalVariable) -> Self::Output {
419 <&DimensionalVariable as Sub<&DimensionalVariable>>::sub(self, &rhs)
420 }
421}
422
423impl<'a, 'b> Mul<&'b DimensionalVariable> for &'a DimensionalVariable {
424 type Output = DimensionalVariable;
425 fn mul(self, rhs: &'b DimensionalVariable) -> Self::Output {
426 DimensionalVariable { value: self.value * rhs.value, unit: add_unit_exponents(self.unit, rhs.unit) }
427 }
428}
429
430impl Mul<DimensionalVariable> for DimensionalVariable {
431 type Output = DimensionalVariable;
432 fn mul(self, rhs: DimensionalVariable) -> Self::Output {
433 <&DimensionalVariable as Mul<&DimensionalVariable>>::mul(&self, &rhs)
434 }
435}
436
437impl<'b> Mul<&'b DimensionalVariable> for DimensionalVariable {
438 type Output = DimensionalVariable;
439 fn mul(self, rhs: &'b DimensionalVariable) -> Self::Output {
440 <&DimensionalVariable as Mul<&DimensionalVariable>>::mul(&self, rhs)
441 }
442}
443
444impl<'a> Mul<DimensionalVariable> for &'a DimensionalVariable {
445 type Output = DimensionalVariable;
446 fn mul(self, rhs: DimensionalVariable) -> Self::Output {
447 <&DimensionalVariable as Mul<&DimensionalVariable>>::mul(self, &rhs)
448 }
449}
450
451impl<'a, 'b> Div<&'b DimensionalVariable> for &'a DimensionalVariable {
452 type Output = DimensionalVariable;
453 fn div(self, rhs: &'b DimensionalVariable) -> Self::Output {
454 DimensionalVariable { value: self.value / rhs.value, unit: sub_unit_exponents(self.unit, rhs.unit) }
455 }
456}
457
458impl Div<DimensionalVariable> for DimensionalVariable {
459 type Output = DimensionalVariable;
460 fn div(self, rhs: DimensionalVariable) -> Self::Output {
461 <&DimensionalVariable as Div<&DimensionalVariable>>::div(&self, &rhs)
462 }
463}
464
465impl<'b> Div<&'b DimensionalVariable> for DimensionalVariable {
466 type Output = DimensionalVariable;
467 fn div(self, rhs: &'b DimensionalVariable) -> Self::Output {
468 <&DimensionalVariable as Div<&DimensionalVariable>>::div(&self, rhs)
469 }
470}
471
472impl<'a> Div<DimensionalVariable> for &'a DimensionalVariable {
473 type Output = DimensionalVariable;
474 fn div(self, rhs: DimensionalVariable) -> Self::Output {
475 <&DimensionalVariable as Div<&DimensionalVariable>>::div(self, &rhs)
476 }
477}
478
479impl AddAssign<&DimensionalVariable> for DimensionalVariable {
481 fn add_assign(&mut self, rhs: &DimensionalVariable) {
482 *self = self.try_add(rhs).expect("Incompatible units for addition assignment");
483 }
484}
485
486impl SubAssign<&DimensionalVariable> for DimensionalVariable {
487 fn sub_assign(&mut self, rhs: &DimensionalVariable) {
488 *self = self.try_sub(rhs).expect("Incompatible units for subtraction assignment");
489 }
490}
491
492impl MulAssign<&DimensionalVariable> for DimensionalVariable {
493 fn mul_assign(&mut self, rhs: &DimensionalVariable) {
494 self.value *= rhs.value;
495 self.unit = add_unit_exponents(self.unit, rhs.unit);
496 }
497}
498
499impl DivAssign<&DimensionalVariable> for DimensionalVariable {
500 fn div_assign(&mut self, rhs: &DimensionalVariable) {
501 self.value /= rhs.value;
502 self.unit = sub_unit_exponents(self.unit, rhs.unit);
503 }
504}
505
506impl<'a> Mul<f64> for &'a DimensionalVariable {
508 type Output = DimensionalVariable;
509 fn mul(self, rhs: f64) -> Self::Output {
510 DimensionalVariable { value: self.value * rhs, unit: self.unit }
511 }
512}
513
514impl Mul<f64> for DimensionalVariable {
515 type Output = DimensionalVariable;
516 fn mul(self, rhs: f64) -> Self::Output {
517 <&DimensionalVariable as Mul<f64>>::mul(&self, rhs)
518 }
519}
520
521impl MulAssign<f64> for DimensionalVariable {
522 fn mul_assign(&mut self, rhs: f64) {
523 self.value *= rhs;
524 }
525}
526
527impl<'a> Div<f64> for &'a DimensionalVariable {
528 type Output = DimensionalVariable;
529 fn div(self, rhs: f64) -> Self::Output {
530 DimensionalVariable { value: self.value / rhs, unit: self.unit }
531 }
532}
533
534impl Div<f64> for DimensionalVariable {
535 type Output = DimensionalVariable;
536 fn div(self, rhs: f64) -> Self::Output {
537 <&DimensionalVariable as Div<f64>>::div(&self, rhs)
538 }
539}
540
541impl DivAssign<f64> for DimensionalVariable {
542 fn div_assign(&mut self, rhs: f64) {
543 self.value /= rhs;
544 }
545}
546
547impl<'a> Mul<&'a DimensionalVariable> for f64 {
549 type Output = DimensionalVariable;
550 fn mul(self, rhs: &'a DimensionalVariable) -> Self::Output {
551 DimensionalVariable { value: self * rhs.value, unit: rhs.unit }
552 }
553}
554
555impl Mul<DimensionalVariable> for f64 {
556 type Output = DimensionalVariable;
557 fn mul(self, rhs: DimensionalVariable) -> Self::Output {
558 <f64 as Mul<&DimensionalVariable>>::mul(self, &rhs)
559 }
560}
561
562impl<'a> Div<&'a DimensionalVariable> for f64 {
563 type Output = DimensionalVariable;
564 fn div(self, rhs: &'a DimensionalVariable) -> Self::Output {
565 DimensionalVariable { value: self / rhs.value, unit: sub_unit_exponents([0.0; units::BASE_UNITS_SIZE], rhs.unit) }
566 }
567}
568
569impl Div<DimensionalVariable> for f64 {
570 type Output = DimensionalVariable;
571 fn div(self, rhs: DimensionalVariable) -> Self::Output {
572 <f64 as Div<&DimensionalVariable>>::div(self, &rhs)
573 }
574}
575
576impl<'a> Neg for &'a DimensionalVariable {
578 type Output = DimensionalVariable;
579 fn neg(self) -> Self::Output {
580 DimensionalVariable { value: -self.value, unit: self.unit }
581 }
582}
583
584impl Neg for DimensionalVariable {
585 type Output = DimensionalVariable;
586 fn neg(self) -> Self::Output {
587 <&DimensionalVariable as Neg>::neg(&self)
588 }
589}
590
591impl PartialEq for DimensionalVariable {
593 fn eq(&self, other: &Self) -> bool {
594 if !self.check_compatibility(other.unit) {
595 return false;
596 }
597 self.value == other.value
598 }
599}
600
601impl PartialOrd for DimensionalVariable {
602 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
603 if !self.check_compatibility(other.unit) { return None; }
604 self.value.partial_cmp(&other.value)
605 }
606}
607
608#[cfg(test)]
609mod tests {
610 use super::read_unit_power;
611
612 #[test]
613 fn read_unit_power_basic_cases() {
614 assert_eq!(read_unit_power("m").unwrap(), ("m", 1));
615 assert_eq!(read_unit_power("m3").unwrap(), ("m", 3));
616 assert_eq!(read_unit_power("m^3").unwrap(), ("m", 3));
617 assert_eq!(read_unit_power("m^-2").unwrap(), ("m", -2));
618 assert_eq!(read_unit_power("m-2").unwrap(), ("m", -2));
619 assert_eq!(read_unit_power(" kg^2 ").unwrap(), ("kg", 2));
620 assert_eq!(read_unit_power("undef").unwrap(), ("undef", 1)); }
622
623 #[test]
624 fn read_unit_power_errors() {
625 assert!(read_unit_power("").is_err());
626 let err = read_unit_power("m^").unwrap_err();
627 assert!(err.contains("Missing exponent"));
628 let err = read_unit_power("^2").unwrap_err();
629 assert!(err.contains("Missing unit symbol"));
630 }
631}