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