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