1#![doc = include_str!("../README.md")]
4#![cfg_attr(
7 feature = "document-features",
8 cfg_attr(doc, doc = ::document_features::document_features!())
9)]
10
11use std::cmp::Ordering;
12use std::num::Saturating;
13
14use num_traits::{checked_pow, ConstOne, ConstZero, PrimInt, ToPrimitive};
15
16mod string;
17pub use string::{DisplayAdapter, EngineeringRepr};
18
19mod float;
20
21#[cfg(feature = "serde")]
22mod serde_support;
23
24#[derive(Debug, Clone, Copy, Default)]
33pub struct EngineeringQuantity<T: EQSupported<T>> {
34 significand: T,
36 exponent: i8,
38}
39
40pub trait EQSupported<T: PrimInt>:
45 PrimInt
46 + std::fmt::Display
47 + ConstZero
48 + ConstOne
49 + SignHelper<T>
50 + TryInto<i64>
51 + TryInto<i128>
52 + TryInto<u64>
53 + TryInto<u128>
54{
55 const EXPONENT_BASE: T;
57}
58
59macro_rules! supported_types {
60 {$($t:ty),+} => {$(
61 impl<> EQSupported<$t> for $t {
62 const EXPONENT_BASE: $t = 1000;
63 }
64 )+}
65}
66
67supported_types!(i16, i32, i64, i128, isize, u16, u32, u64, u128, usize);
68
69#[derive(Debug, Clone)]
71pub struct AbsAndSign<T: PrimInt> {
72 abs: T,
73 negative: bool,
74}
75
76pub trait SignHelper<T: PrimInt> {
81 fn abs_and_sign(&self) -> AbsAndSign<T>;
83}
84
85macro_rules! impl_unsigned_helpers {
86 {$($t:ty),+} => {$(
87 impl<> SignHelper<$t> for $t {
88 fn abs_and_sign(&self) -> AbsAndSign<$t> {
89 AbsAndSign { abs: *self, negative: false }
90 }
91 }
92 )+}
93}
94
95macro_rules! impl_signed_helpers {
96 {$($t:ty),+} => {$(
97 impl<> SignHelper<$t> for $t {
98 fn abs_and_sign(&self) -> AbsAndSign<$t> {
99 AbsAndSign { abs: self.abs(), negative: self.is_negative() }
100 }
101 }
102 )+}
103}
104
105impl_unsigned_helpers!(u16, u32, u64, u128, usize);
106impl_signed_helpers!(i16, i32, i64, i128, isize);
107
108impl<T: EQSupported<T>> EngineeringQuantity<T> {
113 pub fn from_raw(significand: T, exponent: i8) -> Result<Self, Error> {
117 Self::from_raw_unchecked(significand, exponent).check_for_int_overflow()
118 }
119 #[must_use]
121 pub fn to_raw(self) -> (T, i8) {
122 (self.significand, self.exponent)
123 }
124 fn from_raw_unchecked(significand: T, exponent: i8) -> Self {
126 Self {
127 significand,
128 exponent,
129 }
130 }
131}
132
133impl<T: EQSupported<T> + From<EngineeringQuantity<T>>> PartialEq for EngineeringQuantity<T> {
136 fn eq(&self, other: &Self) -> bool {
146 if self.exponent == other.exponent {
148 return self.significand == other.significand;
149 }
150 let cmp = self.partial_cmp(other);
151 matches!(cmp, Some(Ordering::Equal))
152 }
153}
154
155impl<T: EQSupported<T> + From<EngineeringQuantity<T>>> Eq for EngineeringQuantity<T> {}
156
157impl<T: EQSupported<T> + From<EngineeringQuantity<T>>> PartialOrd for EngineeringQuantity<T> {
158 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
159 Some(self.cmp(other))
160 }
161}
162
163impl<T: EQSupported<T> + From<EngineeringQuantity<T>>> Ord for EngineeringQuantity<T> {
164 fn cmp(&self, other: &Self) -> Ordering {
174 if self.exponent == other.exponent {
175 return self.significand.cmp(&other.significand);
176 }
177 let diff = self.exponent - other.exponent;
179 let diff_abs: u32 = diff.unsigned_abs().into();
180 if diff < 0 {
181 let scaled = other.significand * T::EXPONENT_BASE.pow(diff_abs);
182 self.significand.cmp(&scaled)
183 } else {
184 let scaled_self = self.significand * T::EXPONENT_BASE.pow(diff_abs);
185 scaled_self.cmp(&other.significand)
186 }
187 }
188}
189
190impl<T: EQSupported<T>> EngineeringQuantity<T> {
192 pub fn convert<U: EQSupported<U> + From<T>>(&self) -> EngineeringQuantity<U> {
202 let (sig, exp) = self.to_raw();
203 EngineeringQuantity::<U>::from_raw_unchecked(sig.into(), exp)
204 }
205
206 pub fn try_convert<U: EQSupported<U> + TryFrom<T>>(
216 &self,
217 ) -> Result<EngineeringQuantity<U>, Error> {
218 let (sig, exp) = self.to_raw();
219 EngineeringQuantity::<U>::from_raw(sig.try_into().map_err(|_| Error::Overflow)?, exp)
220 }
221
222 #[must_use]
224 pub fn normalise(self) -> Self {
225 let mut working = self;
226 loop {
227 let (div, rem) = (
228 working.significand / T::EXPONENT_BASE,
229 working.significand % T::EXPONENT_BASE,
230 );
231 if rem != T::ZERO {
232 break;
233 }
234 working.significand = div;
235 working.exponent += 1;
236 }
237 working
238 }
239}
240
241impl<T: EQSupported<T>, U: EQSupported<U>> From<T> for EngineeringQuantity<U>
245where
246 U: From<T>,
247{
248 fn from(value: T) -> Self {
255 Self {
256 significand: value.into(),
257 exponent: 0,
258 }
259 }
260}
261
262impl<T: EQSupported<T>> EngineeringQuantity<T> {
266 fn check_for_int_overflow(self) -> Result<Self, Error> {
267 let exp: usize = self.exponent.unsigned_abs().into();
268 let Some(factor) = checked_pow(T::EXPONENT_BASE, exp) else {
269 return Err(if self.exponent < 0 {
270 Error::Underflow
271 } else {
272 Error::Overflow
273 });
274 };
275 let result: T = factor
276 .checked_mul(&self.significand)
277 .ok_or(Error::Overflow)?;
278 let _ = std::convert::TryInto::<T>::try_into(result).map_err(|_| Error::Overflow)?;
279 Ok(self)
280 }
281}
282
283macro_rules! impl_from {
284 {$($t:ty),+} => {$(
285 impl<T: EQSupported<T>> From<EngineeringQuantity<T>> for $t
286 where $t: From<T>,
287 {
288 fn from(eq: EngineeringQuantity<T>) -> Self {
297 let abs_exp: u32 = eq.exponent.unsigned_abs().into();
298 let factor: Saturating<Self> = Saturating(T::EXPONENT_BASE.into());
299 let factor = factor.pow(abs_exp);
300 if eq.exponent > 0 {
301 Self::from(eq.significand) * factor.0
302 } else {
303 Self::from(eq.significand) / factor.0
304 }
305 }
306 }
307
308 )+}
309}
310
311impl_from!(u16, u32, u64, u128, usize, i16, i32, i64, i128, isize);
312
313impl<T: EQSupported<T>> EngineeringQuantity<T> {
314 fn apply_factor<U: EQSupported<U>>(self, sig: U) -> Option<U> {
315 let abs_exp: usize = self.exponent.unsigned_abs().into();
316 let factor = checked_pow(U::EXPONENT_BASE, abs_exp)?;
317 Some(if self.exponent >= 0 {
318 sig * factor
319 } else {
320 sig / factor
321 })
322 }
323}
324
325impl<T: EQSupported<T>> ToPrimitive for EngineeringQuantity<T>
326where
327 f64: TryFrom<EngineeringQuantity<T>>,
328{
329 fn to_i64(&self) -> Option<i64> {
341 let i: i64 = match self.significand.try_into() {
342 Ok(ii) => ii,
343 Err(_) => return None,
344 };
345 self.apply_factor(i)
346 }
347
348 fn to_u64(&self) -> Option<u64> {
349 let i: u64 = match self.significand.try_into() {
350 Ok(ii) => ii,
351 Err(_) => return None,
352 };
353 self.apply_factor(i)
354 }
355
356 fn to_i128(&self) -> Option<i128> {
358 let i: i128 = match self.significand.try_into() {
359 Ok(ii) => ii,
360 Err(_) => return None,
361 };
362 self.apply_factor(i)
363 }
364
365 fn to_u128(&self) -> Option<u128> {
367 let i: u128 = match self.significand.try_into() {
368 Ok(ii) => ii,
369 Err(_) => return None,
370 };
371 self.apply_factor(i)
372 }
373
374 fn to_f64(&self) -> Option<f64> {
392 f64::try_from(*self).ok()
393 }
394}
395
396#[derive(Clone, Copy, Debug, PartialEq, thiserror::Error)]
401#[allow(missing_docs)]
402pub enum Error {
403 #[error("Numeric overflow")]
404 Overflow,
405 #[error("Numeric underflow")]
406 Underflow,
407 #[error("The string could not be parsed")]
408 ParseError,
409 #[error("The conversion could not be completed precisely")]
410 ImpreciseConversion,
411}
412
413#[cfg(test)]
416mod test {
417 use assertables::{assert_gt, assert_lt};
418
419 use super::EngineeringQuantity as EQ;
420 use super::Error as EQErr;
421
422 #[test]
423 fn integers() {
424 for i in &[1i64, -1, 100, -100, 1000, 4000, -4000, 4_000_000] {
425 let ee = EQ::from_raw(*i, 0).unwrap();
426 assert_eq!(i64::from(ee), *i);
427 let ee2 = EQ::from_raw(*i, 1).unwrap();
428 assert_eq!(i64::from(ee2), *i * 1000, "input is {}", *i);
429 }
430 }
431
432 #[test]
433 fn equality() {
434 for (a, b, c, d) in &[
435 (1i64, 0, 1i64, 0),
436 (1, 1, 1000, 0),
437 (2000, 0, 2, 1),
438 (123_000_000, 0, 123_000, 1),
439 (123_000_000, 0, 123, 2),
440 (456_000_000_000_000, 0, 456_000, 3),
441 (456_000_000_000_000, 0, 456, 4),
442 ] {
443 let e1 = EQ::from_raw(*a, *b).unwrap();
444 let e2 = EQ::from_raw(*c, *d).unwrap();
445 assert_eq!(e1, e2);
446 }
447 }
448 #[test]
449 fn comparison() {
450 for (a, b, c, d) in &[
451 (1, 0i8, 2, 0i8),
452 (1, 1, 2, 1),
453 (1001, -1, 1002, -1),
454 (4, -1, 4, -2),
455 (400, -1, 400, -2),
456 ] {
457 let e1 = EQ::from_raw(*a, *b).unwrap();
458 let e2 = EQ::from_raw(*c, *d).unwrap();
459 assert_ne!(e1, e2);
460 }
461 let a1 = EQ::from_raw(1, 2).unwrap();
462 let a2 = EQ::from_raw(2, 2).unwrap();
463 assert_gt!(a2, a1);
464 assert_lt!(a1, a2);
465 }
466
467 #[test]
468 fn conversion() {
469 let t = EQ::<u32>::from_raw(12345, 0).unwrap();
470 let u = t.convert::<u64>();
471 assert_eq!(u.to_raw().0, <u32 as Into<u64>>::into(t.to_raw().0));
472 assert_eq!(t.to_raw().1, u.to_raw().1);
473 }
474
475 #[test]
476 fn to_primitive_underflow() {
477 let _ = EQ::from_raw(1i64, -10).expect_err("underflow");
478 }
479
480 #[test]
481 fn overflow() {
482 let t = EQ::<u32>::from_raw(100_000, 0).unwrap();
484 let _ = t.try_convert::<u16>().expect_err("TryFromIntError");
485
486 assert_eq!(EQ::<u32>::from_raw(1, 5), Err(EQErr::Overflow));
488
489 assert_eq!(EQ::<u64>::from_raw(100_000, 5), Err(EQErr::Overflow));
491 }
492
493 #[test]
494 fn normalise() {
495 let q = EQ::from_raw(1_000_000, 0).unwrap();
496 let q2 = q.normalise();
497 assert_eq!(q, q2);
498 assert_eq!(q2.to_raw(), (1, 2));
499 }
500
501 #[test]
502 fn to_primitive() {
503 use num_traits::ToPrimitive as _;
504 let e = EQ::<i128>::from_raw(1234, 0).unwrap();
505 assert_eq!(e.to_i8(), None);
506 assert_eq!(e.to_i16(), Some(1234));
507 assert_eq!(e.to_i32(), Some(1234));
508 assert_eq!(e.to_i64(), Some(1234));
509 assert_eq!(e.to_i128(), Some(1234));
510 assert_eq!(e.to_isize(), Some(1234));
511 assert_eq!(e.to_u8(), None);
512 assert_eq!(e.to_u16(), Some(1234));
513 assert_eq!(e.to_u32(), Some(1234));
514 assert_eq!(e.to_u64(), Some(1234));
515 assert_eq!(e.to_u128(), Some(1234));
516 assert_eq!(e.to_usize(), Some(1234));
517
518 let e = EQ::<i128>::from_raw(-1, 0).unwrap();
520 assert_eq!(e.to_u64(), None);
521 assert_eq!(e.to_u128(), None);
522
523 let e = EQ::<u128>::from_raw(u128::MAX, 0).unwrap();
525 assert_eq!(e.to_i64(), None);
526 assert_eq!(e.to_i128(), None);
527
528 let e = EQ::from_raw(1, -1).unwrap();
530 assert_eq!(e.to_i32(), Some(0));
531 let e = EQ::from_raw(1001, -1).unwrap();
532 assert_eq!(e.to_i32(), Some(1));
533 let e = EQ::from_raw(-1, -1).unwrap();
534 assert_eq!(e.to_i32(), Some(0));
535 let e = EQ::from_raw(-1001, -1).unwrap();
536 assert_eq!(e.to_i32(), Some(-1));
537 }
538}