1use crate::dimension::Rational16;
4use crate::error::{UnitError, UnitResult};
5use crate::unit::base::BaseUnit;
6use crate::unit::Unit;
7use std::fmt;
8use std::ops::{Add, Div, Mul, Neg, Sub};
9
10#[derive(Clone, Debug)]
33pub struct Quantity {
34 value: f64,
35 unit: Unit,
36}
37
38impl Quantity {
39 pub fn new(value: f64, unit: Unit) -> Self {
41 Quantity { value, unit }
42 }
43
44 pub fn value(&self) -> f64 {
46 self.value
47 }
48
49 pub fn unit(&self) -> &Unit {
51 &self.unit
52 }
53
54 pub fn is_dimensionless(&self) -> bool {
56 self.unit.is_dimensionless()
57 }
58
59 pub fn dimensionless_value(&self) -> UnitResult<f64> {
63 if !self.is_dimensionless() {
64 return Err(UnitError::NotDimensionless);
65 }
66 Ok(self.value * self.unit.scale())
67 }
68
69 pub fn to(&self, target: impl Into<Unit>) -> UnitResult<Quantity> {
73 let target = target.into();
74 if self.unit.dimension() != target.dimension() {
75 return Err(UnitError::DimensionMismatch {
76 from: self.unit.to_string(),
77 to: target.to_string(),
78 });
79 }
80 let si_value = self.unit.to_si(self.value);
81 Ok(Quantity::new(target.from_si(si_value), target))
82 }
83
84 pub fn to_value(&self, target: impl Into<Unit>) -> UnitResult<f64> {
88 Ok(self.to(target)?.value())
89 }
90
91 pub fn decompose(&self) -> Quantity {
93 Quantity::new(
94 self.unit.to_si(self.value),
95 Unit::Composite(crate::unit::composite::CompositeUnit::new(
96 1.0,
97 self.dimension_components(),
98 )),
99 )
100 }
101
102 fn dimension_components(&self) -> Vec<crate::unit::composite::UnitComponent> {
104 use crate::dimension::{Dimension, Rational16};
105 use crate::unit::composite::UnitComponent;
106
107 let dim = self.unit.dimension();
108 let mut components = Vec::new();
109
110 let add_if_nonzero =
111 |comps: &mut Vec<UnitComponent>, symbol: &str, base_dim: Dimension, exp: Rational16| {
112 if !exp.is_zero() {
113 comps.push(UnitComponent::new(symbol, base_dim, 1.0, exp));
114 }
115 };
116
117 add_if_nonzero(&mut components, "m", Dimension::LENGTH, dim.length);
118 add_if_nonzero(&mut components, "s", Dimension::TIME, dim.time);
119 add_if_nonzero(&mut components, "kg", Dimension::MASS, dim.mass);
120 add_if_nonzero(&mut components, "A", Dimension::CURRENT, dim.current);
121 add_if_nonzero(
122 &mut components,
123 "K",
124 Dimension::TEMPERATURE,
125 dim.temperature,
126 );
127 add_if_nonzero(&mut components, "rad", Dimension::ANGLE, dim.angle);
128 add_if_nonzero(
129 &mut components,
130 "sr",
131 Dimension::SOLID_ANGLE,
132 dim.solid_angle,
133 );
134 add_if_nonzero(
135 &mut components,
136 "cd",
137 Dimension::LUMINOUS_INTENSITY,
138 dim.luminous_intensity,
139 );
140 add_if_nonzero(&mut components, "mag", Dimension::MAGNITUDE, dim.magnitude);
141 add_if_nonzero(&mut components, "mol", Dimension::AMOUNT, dim.amount);
142 add_if_nonzero(&mut components, "ph", Dimension::PHOTON, dim.photon);
143
144 components
145 }
146
147 pub fn pow(&self, exp: impl Into<Rational16>) -> Quantity {
154 let exp = exp.into();
155 let value = if exp.denom == 1 {
156 self.value.powi(exp.numer as i32)
157 } else {
158 self.value.powf(exp.to_f64())
159 };
160 Quantity::new(value, self.unit.pow(exp))
161 }
162
163 pub fn sqrt(&self) -> Quantity {
165 Quantity::new(self.value.sqrt(), self.unit.sqrt())
166 }
167
168 pub fn abs(&self) -> Quantity {
170 Quantity::new(self.value.abs(), self.unit.clone())
171 }
172
173 pub fn is_logarithmic(&self) -> bool {
199 self.unit.dimension() == crate::dimension::Dimension::MAGNITUDE
200 }
201
202 pub fn mag_to_flux_ratio(&self) -> UnitResult<f64> {
224 if !self.is_logarithmic() {
225 return Err(UnitError::LogarithmicError(
226 "quantity is not a magnitude".to_string(),
227 ));
228 }
229 let mag = self.value * self.unit.scale();
231 Ok(10.0_f64.powf(-0.4 * mag))
232 }
233
234 pub fn db_to_power_ratio(&self) -> UnitResult<f64> {
256 if !self.is_logarithmic() {
257 return Err(UnitError::LogarithmicError(
258 "quantity is not in decibels".to_string(),
259 ));
260 }
261 let db = self.value * self.unit.scale();
262 Ok(10.0_f64.powf(db / 10.0))
263 }
264
265 pub fn dex_to_ratio(&self) -> UnitResult<f64> {
287 if !self.is_logarithmic() {
288 return Err(UnitError::LogarithmicError(
289 "quantity is not in dex".to_string(),
290 ));
291 }
292 let dex = self.value * self.unit.scale();
293 Ok(10.0_f64.powf(dex))
294 }
295}
296
297impl Quantity {
302 pub fn checked_add(&self, rhs: &Quantity) -> UnitResult<Quantity> {
315 if self.unit.dimension() != rhs.unit.dimension() {
316 return Err(UnitError::IncompatibleDimensions {
317 lhs: self.unit.to_string(),
318 rhs: rhs.unit.to_string(),
319 });
320 }
321 let rhs_converted = self.unit.from_si(rhs.unit.to_si(rhs.value));
322 Ok(Quantity::new(self.value + rhs_converted, self.unit.clone()))
323 }
324
325 pub fn checked_sub(&self, rhs: &Quantity) -> UnitResult<Quantity> {
338 if self.unit.dimension() != rhs.unit.dimension() {
339 return Err(UnitError::IncompatibleDimensions {
340 lhs: self.unit.to_string(),
341 rhs: rhs.unit.to_string(),
342 });
343 }
344 let rhs_converted = self.unit.from_si(rhs.unit.to_si(rhs.value));
345 Ok(Quantity::new(self.value - rhs_converted, self.unit.clone()))
346 }
347}
348
349pub fn batch_convert(
378 values: &[f64],
379 from: impl Into<Unit>,
380 to: impl Into<Unit>,
381) -> UnitResult<Vec<f64>> {
382 let from = from.into();
383 let to = to.into();
384 let factor = from.conversion_factor(&to)?;
385 Ok(values.iter().map(|v| v * factor).collect())
386}
387
388pub fn batch_convert_into(
406 values: &[f64],
407 from: impl Into<Unit>,
408 to: impl Into<Unit>,
409 out: &mut [f64],
410) -> UnitResult<()> {
411 if values.len() != out.len() {
412 return Err(UnitError::BatchError(format!(
413 "input length {} doesn't match output length {}",
414 values.len(),
415 out.len()
416 )));
417 }
418 let from = from.into();
419 let to = to.into();
420 let factor = from.conversion_factor(&to)?;
421 for (i, v) in values.iter().enumerate() {
422 out[i] = v * factor;
423 }
424 Ok(())
425}
426
427pub fn conversion_factor(from: impl Into<Unit>, to: impl Into<Unit>) -> UnitResult<f64> {
449 let from = from.into();
450 let to = to.into();
451 from.conversion_factor(&to)
452}
453
454impl fmt::Display for Quantity {
455 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
456 let unit_str = self.unit.to_string();
457 if unit_str.is_empty() || unit_str == "dimensionless" {
458 write!(f, "{}", self.value)
459 } else {
460 write!(f, "{} {}", self.value, unit_str)
461 }
462 }
463}
464
465impl PartialEq for Quantity {
466 fn eq(&self, other: &Self) -> bool {
467 if self.unit.dimension() != other.unit.dimension() {
470 return false;
471 }
472 let self_si = self.unit.to_si(self.value);
473 let other_si = other.unit.to_si(other.value);
474 (self_si - other_si).abs() < 1e-15 * self_si.abs().max(other_si.abs()).max(1e-15)
475 }
476}
477
478impl Add for Quantity {
482 type Output = Quantity;
483
484 fn add(self, rhs: Quantity) -> Quantity {
485 if self.unit.dimension() != rhs.unit.dimension() {
486 panic!(
487 "{}",
488 UnitError::IncompatibleDimensions {
489 lhs: self.unit.to_string(),
490 rhs: rhs.unit.to_string(),
491 }
492 );
493 }
494 let rhs_converted = self.unit.from_si(rhs.unit.to_si(rhs.value));
495 Quantity::new(self.value + rhs_converted, self.unit)
496 }
497}
498
499impl Add for &Quantity {
500 type Output = Quantity;
501
502 fn add(self, rhs: &Quantity) -> Quantity {
503 self.checked_add(rhs).unwrap_or_else(|e| panic!("{}", e))
504 }
505}
506
507impl Sub for Quantity {
511 type Output = Quantity;
512
513 fn sub(self, rhs: Quantity) -> Quantity {
514 if self.unit.dimension() != rhs.unit.dimension() {
515 panic!(
516 "{}",
517 UnitError::IncompatibleDimensions {
518 lhs: self.unit.to_string(),
519 rhs: rhs.unit.to_string(),
520 }
521 );
522 }
523 let rhs_converted = self.unit.from_si(rhs.unit.to_si(rhs.value));
524 Quantity::new(self.value - rhs_converted, self.unit)
525 }
526}
527
528impl Sub for &Quantity {
529 type Output = Quantity;
530
531 fn sub(self, rhs: &Quantity) -> Quantity {
532 self.checked_sub(rhs).unwrap_or_else(|e| panic!("{}", e))
533 }
534}
535
536impl Mul for Quantity {
538 type Output = Quantity;
539
540 fn mul(self, rhs: Quantity) -> Quantity {
541 Quantity::new(self.value * rhs.value, self.unit * rhs.unit)
542 }
543}
544
545impl Mul for &Quantity {
546 type Output = Quantity;
547
548 fn mul(self, rhs: &Quantity) -> Quantity {
549 Quantity::new(self.value * rhs.value, &self.unit * &rhs.unit)
550 }
551}
552
553impl Mul<&Quantity> for Quantity {
554 type Output = Quantity;
555
556 fn mul(self, rhs: &Quantity) -> Quantity {
557 Quantity::new(self.value * rhs.value, self.unit * &rhs.unit)
558 }
559}
560
561impl Mul<Quantity> for &Quantity {
562 type Output = Quantity;
563
564 fn mul(self, rhs: Quantity) -> Quantity {
565 Quantity::new(self.value * rhs.value, &self.unit * rhs.unit)
566 }
567}
568
569impl Div for Quantity {
571 type Output = Quantity;
572
573 fn div(self, rhs: Quantity) -> Quantity {
574 Quantity::new(self.value / rhs.value, self.unit / rhs.unit)
575 }
576}
577
578impl Div for &Quantity {
579 type Output = Quantity;
580
581 fn div(self, rhs: &Quantity) -> Quantity {
582 Quantity::new(self.value / rhs.value, &self.unit / &rhs.unit)
583 }
584}
585
586impl Div<&Quantity> for Quantity {
587 type Output = Quantity;
588
589 fn div(self, rhs: &Quantity) -> Quantity {
590 Quantity::new(self.value / rhs.value, self.unit / &rhs.unit)
591 }
592}
593
594impl Div<Quantity> for &Quantity {
595 type Output = Quantity;
596
597 fn div(self, rhs: Quantity) -> Quantity {
598 Quantity::new(self.value / rhs.value, &self.unit / rhs.unit)
599 }
600}
601
602impl Mul<f64> for Quantity {
604 type Output = Quantity;
605
606 fn mul(self, rhs: f64) -> Quantity {
607 Quantity::new(self.value * rhs, self.unit)
608 }
609}
610
611impl Mul<f64> for &Quantity {
612 type Output = Quantity;
613
614 fn mul(self, rhs: f64) -> Quantity {
615 Quantity::new(self.value * rhs, self.unit.clone())
616 }
617}
618
619impl Mul<Quantity> for f64 {
621 type Output = Quantity;
622
623 fn mul(self, rhs: Quantity) -> Quantity {
624 Quantity::new(self * rhs.value, rhs.unit)
625 }
626}
627
628impl Mul<&Quantity> for f64 {
629 type Output = Quantity;
630
631 fn mul(self, rhs: &Quantity) -> Quantity {
632 Quantity::new(self * rhs.value, rhs.unit.clone())
633 }
634}
635
636impl Div<f64> for Quantity {
638 type Output = Quantity;
639
640 fn div(self, rhs: f64) -> Quantity {
641 Quantity::new(self.value / rhs, self.unit)
642 }
643}
644
645impl Div<f64> for &Quantity {
646 type Output = Quantity;
647
648 fn div(self, rhs: f64) -> Quantity {
649 Quantity::new(self.value / rhs, self.unit.clone())
650 }
651}
652
653impl Div<Quantity> for f64 {
655 type Output = Quantity;
656
657 fn div(self, rhs: Quantity) -> Quantity {
658 Quantity::new(self / rhs.value, rhs.unit.inv())
659 }
660}
661
662impl Div<&Quantity> for f64 {
663 type Output = Quantity;
664
665 fn div(self, rhs: &Quantity) -> Quantity {
666 Quantity::new(self / rhs.value, rhs.unit.inv())
667 }
668}
669
670impl Neg for Quantity {
672 type Output = Quantity;
673
674 fn neg(self) -> Quantity {
675 Quantity::new(-self.value, self.unit)
676 }
677}
678
679impl Neg for &Quantity {
680 type Output = Quantity;
681
682 fn neg(self) -> Quantity {
683 Quantity::new(-self.value, self.unit.clone())
684 }
685}
686
687impl Mul<Unit> for f64 {
689 type Output = Quantity;
690
691 fn mul(self, unit: Unit) -> Quantity {
692 Quantity::new(self, unit)
693 }
694}
695
696impl Mul<&Unit> for f64 {
697 type Output = Quantity;
698
699 fn mul(self, unit: &Unit) -> Quantity {
700 Quantity::new(self, unit.clone())
701 }
702}
703
704impl Mul<BaseUnit> for f64 {
706 type Output = Quantity;
707
708 fn mul(self, unit: BaseUnit) -> Quantity {
709 Quantity::new(self, Unit::from(unit))
710 }
711}
712
713impl Mul<f64> for BaseUnit {
715 type Output = Quantity;
716
717 fn mul(self, value: f64) -> Quantity {
718 Quantity::new(value, Unit::from(self))
719 }
720}
721
722impl Mul<BaseUnit> for Quantity {
724 type Output = Quantity;
725
726 fn mul(self, unit: BaseUnit) -> Quantity {
727 self * Unit::from(unit)
728 }
729}
730
731impl Mul<BaseUnit> for &Quantity {
732 type Output = Quantity;
733
734 fn mul(self, unit: BaseUnit) -> Quantity {
735 Quantity::new(self.value, &self.unit * unit)
736 }
737}
738
739impl Div<BaseUnit> for Quantity {
741 type Output = Quantity;
742
743 fn div(self, unit: BaseUnit) -> Quantity {
744 self / Unit::from(unit)
745 }
746}
747
748impl Div<BaseUnit> for &Quantity {
749 type Output = Quantity;
750
751 fn div(self, unit: BaseUnit) -> Quantity {
752 Quantity::new(self.value, &self.unit / unit)
753 }
754}
755
756impl Mul<Unit> for Quantity {
758 type Output = Quantity;
759
760 fn mul(self, unit: Unit) -> Quantity {
761 Quantity::new(self.value, self.unit * unit)
762 }
763}
764
765impl Mul<&Unit> for Quantity {
766 type Output = Quantity;
767
768 fn mul(self, unit: &Unit) -> Quantity {
769 Quantity::new(self.value, self.unit * unit)
770 }
771}
772
773impl Mul<Unit> for &Quantity {
774 type Output = Quantity;
775
776 fn mul(self, unit: Unit) -> Quantity {
777 Quantity::new(self.value, &self.unit * unit)
778 }
779}
780
781impl Mul<&Unit> for &Quantity {
782 type Output = Quantity;
783
784 fn mul(self, unit: &Unit) -> Quantity {
785 Quantity::new(self.value, &self.unit * unit)
786 }
787}
788
789impl Div<Unit> for Quantity {
791 type Output = Quantity;
792
793 fn div(self, unit: Unit) -> Quantity {
794 Quantity::new(self.value, self.unit / unit)
795 }
796}
797
798impl Div<&Unit> for Quantity {
799 type Output = Quantity;
800
801 fn div(self, unit: &Unit) -> Quantity {
802 Quantity::new(self.value, self.unit / unit)
803 }
804}
805
806impl Div<Unit> for &Quantity {
807 type Output = Quantity;
808
809 fn div(self, unit: Unit) -> Quantity {
810 Quantity::new(self.value, &self.unit / unit)
811 }
812}
813
814impl Div<&Unit> for &Quantity {
815 type Output = Quantity;
816
817 fn div(self, unit: &Unit) -> Quantity {
818 Quantity::new(self.value, &self.unit / unit)
819 }
820}
821
822#[cfg(test)]
823mod tests {
824 use super::*;
825 use crate::dimension::Dimension;
826 use crate::unit::base::BaseUnit;
827
828 fn meter() -> Unit {
829 Unit::Base(BaseUnit::new("meter", "m", &[], Dimension::LENGTH, 1.0))
830 }
831
832 fn kilometer() -> Unit {
833 Unit::Base(BaseUnit::new(
834 "kilometer",
835 "km",
836 &[],
837 Dimension::LENGTH,
838 1000.0,
839 ))
840 }
841
842 fn second() -> Unit {
843 Unit::Base(BaseUnit::new("second", "s", &[], Dimension::TIME, 1.0))
844 }
845
846 #[test]
847 fn test_quantity_creation() {
848 let q = 5.0 * meter();
849 assert_eq!(q.value(), 5.0);
850 }
851
852 #[test]
853 fn test_quantity_conversion() {
854 let q = 1.0 * kilometer();
855 let q_m = q.to(&meter()).unwrap();
856 assert!((q_m.value() - 1000.0).abs() < 1e-10);
857 }
858
859 #[test]
860 fn test_quantity_addition() {
861 let a = 1.0 * kilometer();
862 let b = 500.0 * meter();
863 let c = a + b;
864 assert!((c.value() - 1.5).abs() < 1e-10); }
866
867 #[test]
868 fn test_quantity_subtraction() {
869 let a = 1.0 * kilometer();
870 let b = 500.0 * meter();
871 let c = a - b;
872 assert!((c.value() - 0.5).abs() < 1e-10); }
874
875 #[test]
876 fn test_quantity_multiplication() {
877 let a = 10.0 * meter();
878 let b = 5.0 * second();
879 let c = a * b;
880 assert!((c.value() - 50.0).abs() < 1e-10);
881 }
882
883 #[test]
884 fn test_quantity_division() {
885 let dist = 100.0 * meter();
886 let time = 10.0 * second();
887 let speed = dist / time;
888 assert!((speed.value() - 10.0).abs() < 1e-10);
889 }
890
891 #[test]
892 #[should_panic(expected = "cannot add/subtract quantities with different dimensions")]
893 fn test_incompatible_addition() {
894 let a = 1.0 * meter();
895 let b = 1.0 * second();
896 let _ = a + b;
897 }
898
899 #[test]
900 fn test_checked_add_incompatible() {
901 let a = 1.0 * meter();
902 let b = 1.0 * second();
903 let result = a.checked_add(&b);
904 assert!(matches!(
905 result,
906 Err(UnitError::IncompatibleDimensions { .. })
907 ));
908 }
909
910 #[test]
911 fn test_checked_sub_incompatible() {
912 let a = 1.0 * meter();
913 let b = 1.0 * second();
914 let result = a.checked_sub(&b);
915 assert!(matches!(
916 result,
917 Err(UnitError::IncompatibleDimensions { .. })
918 ));
919 }
920
921 #[test]
922 fn test_quantity_display() {
923 let q = 5.5 * meter();
924 assert_eq!(format!("{}", q), "5.5 m");
925 }
926
927 #[test]
928 fn test_ref_addition_no_clone() {
929 let a = 1.0 * kilometer();
931 let b = 500.0 * meter();
932 let c = &a + &b;
933 assert!((c.value() - 1.5).abs() < 1e-10); assert!((a.value() - 1.0).abs() < 1e-10);
936 assert!((b.value() - 500.0).abs() < 1e-10);
937 }
938
939 #[test]
940 fn test_ref_subtraction_no_clone() {
941 let a = 1.0 * kilometer();
943 let b = 500.0 * meter();
944 let c = &a - &b;
945 assert!((c.value() - 0.5).abs() < 1e-10); }
947
948 #[test]
949 fn test_batch_convert() {
950 use super::batch_convert;
951 let values = vec![1.0, 2.0, 3.0, 100.0];
952 let converted = batch_convert(&values, &kilometer(), &meter()).unwrap();
953 assert_eq!(converted.len(), 4);
954 assert!((converted[0] - 1000.0).abs() < 1e-10);
955 assert!((converted[1] - 2000.0).abs() < 1e-10);
956 assert!((converted[2] - 3000.0).abs() < 1e-10);
957 assert!((converted[3] - 100000.0).abs() < 1e-10);
958 }
959
960 #[test]
961 fn test_batch_convert_into() {
962 use super::batch_convert_into;
963 let values = [1.0, 2.0, 3.0];
964 let mut out = [0.0; 3];
965 batch_convert_into(&values, &kilometer(), &meter(), &mut out).unwrap();
966 assert!((out[0] - 1000.0).abs() < 1e-10);
967 assert!((out[1] - 2000.0).abs() < 1e-10);
968 assert!((out[2] - 3000.0).abs() < 1e-10);
969 }
970
971 #[test]
972 fn test_batch_convert_into_length_mismatch() {
973 use super::batch_convert_into;
974 let values = [1.0, 2.0, 3.0];
975 let mut out = [0.0; 2]; let result = batch_convert_into(&values, &kilometer(), &meter(), &mut out);
977 assert!(result.is_err());
978 }
979
980 #[test]
981 fn test_batch_convert_incompatible_units() {
982 use super::batch_convert;
983 let values = vec![1.0, 2.0];
984 let result = batch_convert(&values, &meter(), &second());
985 assert!(result.is_err());
986 }
987
988 #[test]
989 fn test_conversion_factor() {
990 use super::conversion_factor;
991 let factor = conversion_factor(&kilometer(), &meter()).unwrap();
992 assert!((factor - 1000.0).abs() < 1e-10);
993 }
994
995 #[test]
996 fn test_pow_integer() {
997 let m = 3.0 * meter();
998 let m2 = m.pow(2);
999 assert!((m2.value() - 9.0).abs() < 1e-10);
1000 assert_eq!(
1001 m2.unit().dimension(),
1002 Dimension {
1003 length: Rational16::new(2, 1),
1004 ..Dimension::DIMENSIONLESS
1005 }
1006 );
1007 }
1008
1009 #[test]
1010 fn test_pow_fractional() {
1011 let area = 4.0 * meter().pow(Rational16::new(2, 1));
1013 let root = area.pow(Rational16::new(1, 2));
1014 assert!((root.value() - 2.0).abs() < 1e-10);
1015 assert_eq!(root.unit().dimension(), Dimension::LENGTH);
1016 }
1017}