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