standardform/
lib.rs

1#![doc = include_str!("../README.md")]
2#![deny(
3    missing_docs,
4    missing_debug_implementations,
5    unused_mut,
6    unused_allocation,
7    unused_must_use,
8    unreachable_patterns,
9    unstable_features,
10    trivial_casts,
11    overflowing_literals,
12    clippy::cargo,
13    clippy::perf,
14    clippy::complexity,
15    clippy::style,
16    clippy::suspicious
17)]
18#![cfg_attr(
19    not(feature = "python"),
20    forbid(non_snake_case, unsafe_op_in_unsafe_fn, unsafe_code)
21)]
22#![cfg_attr(
23    feature = "python",
24    allow(non_snake_case, unsafe_op_in_unsafe_fn, unsafe_code)
25)]
26
27#[cfg(all(feature = "nom", not(feature = "_bindings")))]
28mod nom;
29
30#[cfg(all(feature = "nom", not(feature = "_bindings")))]
31pub use nom::*;
32
33#[cfg(feature = "python")]
34mod python;
35
36#[cfg(feature = "wasm")]
37mod wasm;
38
39use core::f64;
40use core::num::{ParseFloatError, ParseIntError};
41use core::str::Utf8Error;
42
43use decorum::Finite;
44use num_traits::real::Real;
45use num_traits::{One, Zero};
46
47use thiserror::Error;
48
49/// Represents a number in standard form.
50///
51/// The `Standardform` struct holds the significand (mantissa) of the number
52/// and an exponent that determines the power of 10 by which the significand should be multiplied.
53#[cfg_attr(feature = "python", pyo3::prelude::pyclass)]
54#[cfg_attr(feature = "wasm", wasm_bindgen::prelude::wasm_bindgen)]
55#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
56pub struct StandardForm {
57    mantissa: Finite<f64>,
58    exponent: i8,
59}
60
61impl StandardForm {
62    /// Creates a new instance of `StandardForm` with the given mantissa and exponent.
63    ///
64    /// This constructor initializes a new `StandardForm` instance with the provided `mantissa` and `exponent`.
65    /// It's important to note that the provided `mantissa` and `exponent` may not be exactly the same as the
66    /// values stored in the resulting instance. The values are adjusted automatically to adhere to the rules
67    /// of standard form representation, ensuring the most appropriate form for the given input.
68    ///
69    ///  ## Rules :
70    /// If the current mantissa and exponent do not satisfy the standard form representation requirements,
71    /// this method will adjust them while maintaining the value of the number represented. The adjustment
72    /// ensures that the mantissa is between 1 (inclusive) and 10 (exclusive) and the exponent is such that
73    /// the product of mantissa and 10 raised to the exponent yields the original number.
74    pub fn new(mantissa: Finite<f64>, exponent: i8) -> Self {
75        let mut instance = Self::new_unchecked(mantissa, exponent);
76        instance.adjust();
77        instance
78    }
79
80    /// Returns the 'real' value of the struct
81    pub fn as_finite(self) -> Finite<f64> {
82        self.mantissa * 10f64.powi(self.exponent.into())
83    }
84
85    pub(crate) const fn new_unchecked(mantissa: Finite<f64>, exponent: i8) -> Self {
86        Self { mantissa, exponent }
87    }
88
89    fn in_range(&self) -> bool {
90        // https://en.wikipedia.org/wiki/Standard_form
91        // 1 ≤ a < 10 -- Although I include negative numbers as well
92
93        let abs = self.mantissa.abs();
94
95        // 1.0 <= abs is same as abs >= 1.0
96        // aka
97
98        #[allow(clippy::manual_range_contains)]
99        {
100            abs >= 1.0 && abs < 10.0
101        }
102    }
103
104    fn adjust(&mut self) {
105        if self.in_range() || self.mantissa == 0.0 {
106            return;
107        }
108
109        use num_traits::real::Real;
110
111        let log10_mantissa = self.mantissa.abs().log10();
112
113        // Find the largest power of 10
114        let adjustment = log10_mantissa.floor();
115
116        if adjustment != 0.0 {
117            self.mantissa /= Finite::from_inner(10f64).powf(adjustment);
118            self.exponent += adjustment.into_inner() as i8;
119        }
120
121        // Final adjustment if it's still slightly out of range
122        if !self.in_range() {
123            if self.mantissa > -1.0 && self.mantissa < 1.0 {
124                self.mantissa *= 10.0;
125                self.exponent -= 1;
126            } else {
127                self.mantissa /= 10.0;
128                self.exponent += 1;
129            }
130        }
131    }
132}
133
134impl Default for StandardForm {
135    #[must_use]
136    fn default() -> Self {
137        Self::one()
138    }
139}
140
141impl core::fmt::Display for StandardForm {
142    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
143        if self.exponent > 4 {
144            return write!(f, "{}", self.to_scientific_notation());
145        };
146
147        write!(f, "{}", self.mantissa * 10_f64.powi(self.exponent as i32))
148    }
149}
150
151impl StandardForm {
152    /// Returns a reference to the StandardForm representing the significand (mantissa) of the number.
153    #[must_use]
154    pub const fn mantissa(&self) -> &Finite<f64> {
155        &self.mantissa
156    }
157
158    /// Returns the exponent that determines the power of 10 by which the significand should be multiplied.
159    #[must_use]
160    pub const fn exponent(&self) -> &i8 {
161        &self.exponent
162    }
163}
164
165#[cfg_attr(feature = "wasm", wasm_bindgen::prelude::wasm_bindgen)]
166impl StandardForm {
167    /// Returns the string representation of the number in scientific notation.
168    #[must_use]
169    #[cfg(feature = "std")]
170    #[cfg_attr(feature = "wasm", wasm_bindgen::prelude::wasm_bindgen)]
171    pub fn to_scientific_notation(&self) -> String {
172        format!("{}e{}", self.mantissa, self.exponent)
173    }
174
175    /// Returns the string representation of the number in engineering notation.
176    #[must_use]
177    #[cfg(feature = "std")]
178    #[cfg_attr(feature = "wasm", wasm_bindgen::prelude::wasm_bindgen)]
179    pub fn to_engineering_notation(&self) -> String {
180        format!("{}*10^{}", self.mantissa, self.exponent)
181    }
182}
183
184#[cfg(feature = "std")]
185impl TryFrom<&str> for StandardForm {
186    type Error = crate::ParsingStandardFormError;
187
188    fn try_from(value: &str) -> Result<Self, Self::Error> {
189        if let Ok(number) = value.parse::<f64>() {
190            let output: Result<Self, ConversionError> = number.try_into();
191            return output.map_err(|e| e.into());
192        }
193
194        if let Some(index) = value.find('e').or_else(|| value.find('E')) {
195            let m_str: Finite<f64> = value[0..index].parse()?;
196            let e_str: i8 = value[index + 1..].parse()?;
197            return Ok(StandardForm::new(m_str, e_str));
198        }
199
200        if let Some(index) = value.find('^') {
201            let m_str: Finite<f64> = value[0..index - 3].parse()?;
202            let e_str: i8 = value[index + 1..].parse()?;
203            return Ok(StandardForm::new(m_str, e_str));
204        }
205
206        Err(crate::ParsingStandardFormError::InvalidFormat)
207    }
208}
209
210/// Represents the possible errors that can occur during parsing of a `StandardForm` number.
211#[derive(Error, Debug, Clone, PartialEq, Eq)]
212pub enum ParsingStandardFormError {
213    /// Error that occurs while parsing the mantissa as a `ParseFloatError`.
214    #[error("Failed parsing mantissa due to {0}")]
215    Mantissa(#[from] ParseFloatError),
216    /// Error that occurs while parsing the exponent as a `ParseIntError`.
217    #[error("Failed parsing exponent due to {0}")]
218    Exponent(#[from] ParseIntError),
219    /// Indicates an invalid format that doesn't match any valid `StandardForm` notation.
220    #[error("Invalid format")]
221    InvalidFormat,
222    /// Only occurs when `StandardFrom::try_from(&[u8])` is done
223    #[error("Given bytes are not formatted in UTF-8")]
224    InvalidBytes(#[from] Utf8Error),
225
226    /// Only occurs when from_str_radix` method is used
227    #[error("Invalid radix : Only radix 10 is supported")]
228    InvalidRadix,
229
230    /// Constraints Voliated for constructing the given f64
231    #[error("{0}")]
232    ConstraintsVoliated(#[from] ConversionError),
233}
234
235impl core::ops::Neg for StandardForm {
236    type Output = Self;
237    #[must_use]
238    fn neg(self) -> Self::Output {
239        Self::new_unchecked(-self.mantissa, self.exponent)
240    }
241}
242impl core::ops::Add for StandardForm {
243    type Output = Self;
244    fn add(self, other: Self) -> Self {
245        let max_power = self.exponent.max(other.exponent);
246        let num_sum = self.mantissa * 10.0_f64.powf((self.exponent - max_power) as f64)
247            + other.mantissa * 10.0_f64.powf((other.exponent - max_power) as f64);
248        StandardForm::new(num_sum, max_power)
249    }
250}
251
252impl core::ops::AddAssign for StandardForm {
253    fn add_assign(&mut self, other: Self) {
254        let max_power = self.exponent.max(other.exponent);
255        let num_sum = self.mantissa * 10.0_f64.powf((self.exponent - max_power) as f64)
256            + other.mantissa * 10.0_f64.powf((other.exponent - max_power) as f64);
257
258        self.mantissa = num_sum;
259        self.exponent = max_power;
260
261        self.adjust();
262    }
263}
264
265pub(crate) fn round(result: Finite<f64>) -> Finite<f64> {
266    const TOLERANCE: f64 = 1.0e6;
267    (result * TOLERANCE).round() / TOLERANCE
268}
269
270impl core::ops::Sub for StandardForm {
271    type Output = Self;
272    #[must_use]
273    fn sub(self, other: Self) -> Self {
274        let min = self.exponent.min(other.exponent);
275
276        let x = self.mantissa * 10_f64.powi((self.exponent - min) as i32);
277        let y = other.mantissa * 10_f64.powi((other.exponent - min) as i32);
278
279        let result = x - y;
280
281        StandardForm::new(round(result), min)
282    }
283}
284
285impl core::ops::SubAssign for StandardForm {
286    fn sub_assign(&mut self, other: Self) {
287        let min = self.exponent.min(other.exponent);
288
289        let x = self.mantissa * 10_i32.pow((self.exponent - min) as u32) as f64;
290        let y = other.mantissa * 10_i32.pow((other.exponent - min) as u32) as f64;
291
292        self.mantissa = x - y;
293        self.exponent = min;
294        self.adjust();
295    }
296}
297
298impl core::ops::Mul for StandardForm {
299    type Output = Self;
300    #[must_use]
301    fn mul(self, other: Self) -> Self {
302        let exponent = self.exponent + other.exponent;
303        let mantissa = self.mantissa * other.mantissa;
304        StandardForm::new(round(mantissa), exponent)
305    }
306}
307
308impl core::ops::MulAssign for StandardForm {
309    fn mul_assign(&mut self, other: Self) {
310        let exponent = self.exponent + other.exponent;
311        let mantissa = self.mantissa * other.mantissa;
312
313        self.mantissa = round(mantissa);
314        self.exponent = exponent;
315
316        self.adjust();
317    }
318}
319
320impl core::ops::Div for StandardForm {
321    type Output = Self;
322    #[must_use]
323    fn div(self, other: Self) -> Self {
324        StandardForm::new(
325            self.mantissa / other.mantissa,
326            self.exponent - other.exponent,
327        )
328    }
329}
330
331impl core::ops::DivAssign for StandardForm {
332    fn div_assign(&mut self, other: Self) {
333        self.mantissa /= other.mantissa;
334        self.exponent /= other.exponent;
335        self.adjust();
336    }
337}
338
339impl core::ops::Rem for StandardForm {
340    type Output = Self;
341
342    fn rem(self, rhs: Self) -> Self::Output {
343        StandardForm::from(self.as_finite() % rhs.as_finite())
344    }
345}
346
347impl core::ops::RemAssign for StandardForm {
348    fn rem_assign(&mut self, other: StandardForm) {
349        *self = self.clone() % other;
350    }
351}
352
353macro_rules! primitives {
354    (ints => $($t:ty),*) => {
355        $(
356            impl From<$t> for StandardForm {
357                #[must_use]
358                fn from(value: $t) -> Self {
359                    StandardForm::new((value as f64).into(),0)
360                }
361            }
362
363            primitives!(ops => $t);
364        )*
365    };
366
367    (ops => $($t:ty),*) => {
368        $(
369            impl PartialEq<$t> for StandardForm {
370                fn eq(&self,other: &$t) -> bool {
371                    let rhs : Self = (*other).into();
372                    *self == rhs
373                }
374            }
375
376            impl PartialOrd<$t> for StandardForm {
377                fn partial_cmp(&self, other: &$t) -> Option<std::cmp::Ordering> {
378                    let rhs : Self = (*other).into();
379                    self.partial_cmp(&rhs)
380                }
381            }
382
383            impl  core::ops::Add<$t> for StandardForm {
384                type Output = Self;
385                #[must_use]
386                fn add(self, other: $t) -> Self {
387                    let rhs : Self = other.into();
388                    self + rhs
389                }
390            }
391
392            impl  core::ops::AddAssign<$t> for StandardForm {
393                fn add_assign(&mut self, other: $t) {
394                    let rhs : Self = other.into();
395                    *self += rhs;
396                }
397            }
398
399            impl  core::ops::Sub<$t> for StandardForm {
400                type Output = Self;
401                #[must_use]
402                fn sub(self, other: $t) -> Self {
403                    let rhs : Self = other.into();
404                    self - rhs
405                }
406            }
407
408            impl  core::ops::SubAssign<$t> for StandardForm {
409                fn sub_assign(&mut self, other: $t) {
410                    let rhs : Self = other.into();
411                    *self -= rhs;
412                }
413            }
414
415            impl  core::ops::Mul<$t> for StandardForm {
416                type Output = Self;
417                #[must_use]
418                fn mul(self, other: $t) -> Self {
419                    let rhs : Self = other.into();
420                    self * rhs
421                }
422            }
423
424            impl  core::ops::MulAssign<$t> for StandardForm {
425                fn mul_assign(&mut self, other: $t) {
426                    let rhs : Self = other.into();
427                    *self *= rhs;
428                }
429            }
430
431            impl core::ops:: Div<$t> for StandardForm {
432                type Output = Self;
433                #[must_use]
434                fn div(self, other: $t) -> Self {
435                    let rhs : Self = other.into();
436                    self / rhs
437                }
438            }
439
440            impl  core::ops::DivAssign<$t> for StandardForm {
441                fn div_assign(&mut self, other: $t) {
442                    let rhs : Self = other.into();
443                    *self /= rhs;
444                }
445            }
446
447            impl  core::ops::Rem<$t> for StandardForm {
448                type Output = Self;
449                #[must_use]
450                fn rem(self, other: $t) -> Self {
451                    let rhs : Self = other.into();
452                    self % rhs
453                }
454            }
455
456            impl  core::ops::RemAssign<$t> for StandardForm {
457                fn rem_assign(&mut self, other: $t) {
458                    let rhs : Self = other.into();
459                    *self %= rhs;
460                }
461            }
462        )*
463    };
464}
465
466primitives!(ints => u8,u16,u32,u64,i8,i16,i32,i64);
467primitives!(ops => Finite<f64>);
468
469impl From<Finite<f64>> for StandardForm {
470    fn from(value: Finite<f64>) -> Self {
471        Self::new(value, Zero::zero())
472    }
473}
474
475#[cfg_attr(feature = "wasm", wasm_bindgen::prelude::wasm_bindgen)]
476#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
477/// ConversionError
478pub enum ConversionError {
479    /// Constraint of Finite<f64> is voliated by NaN
480    #[error("Constraint of Finite<f64> is voliated by NaN")]
481    NaN,
482
483    /// Constraint of Finite<f64> is voliated by Infinity
484    #[error("Constraint of Finite<f64> is voliated by Infinity")]
485    Infinity,
486}
487
488impl TryFrom<f64> for StandardForm {
489    type Error = ConversionError;
490    fn try_from(value: f64) -> Result<Self, Self::Error> {
491        // Check for NaN
492        if value.is_nan() {
493            return Err(ConversionError::NaN);
494        }
495
496        // Check for Infinity
497        if value.is_infinite() {
498            return Err(ConversionError::Infinity);
499        }
500
501        Ok(Self::from(Finite::from(value)))
502    }
503}
504
505impl TryFrom<f32> for StandardForm {
506    type Error = ConversionError;
507    fn try_from(value: f32) -> Result<Self, Self::Error> {
508        Self::try_from(value as f64)
509    }
510}
511
512// TODO - Trig Functions
513/*macro_rules! trig_functions {
514    ($( {
515        $(#[$attr:meta])* $fn : ident
516    })*) => {
517        impl StandardForm {
518            $(
519                $(#[$attr])*
520                #[cfg(not(feature="js"))]
521                pub fn $fn <T : From<f64>>(self) -> T  {
522                    f64::from(self). $fn ().into()
523                }
524
525                $(#[$attr])*
526                #[cfg(feature="js")]
527                #[cfg_attr(feature="js", wasm_bindgen)]
528                pub fn $fn (self) -> Self  {
529                    f64::from(self). $fn ().into()
530                }
531            )*
532
533        }
534    };
535}
536
537trig_functions!(
538    { /// Computes the sine of a number (in radians).
539      sin }
540    { /// Computes the cosine of a number (in radians).
541      cos }
542    { /// Computes the tangent of a number (in radians).
543     tan }
544    { /// Computes the arcsine of a number.
545      asin }
546    { /// Computes the arccosine of a number.
547      acos }
548    { /// Computes the arctangent of a number.
549      atan }
550    { /// Computes the hyperbolic sine.
551      sinh }
552    { /// Computes the hyperbolic cosine.
553     cosh }
554    { /// Computes the hyperbolic tangent.
555      tanh }
556    { /// Computes the inverse hyperbolic sine.
557      asinh }
558    { /// Computes the inverse hyperbolic cosine.
559      acosh }
560    { /// Computes the inverse hyperbolic tangent.
561      atanh }
562);
563*/
564
565// TODO: Add this to te python API once this is done - https://github.com/PyO3/pyo3/issues/1190
566impl num_traits::Zero for StandardForm {
567    fn zero() -> Self {
568        Self::new_unchecked(Finite::zero(), num_traits::Zero::zero())
569    }
570
571    fn is_zero(&self) -> bool {
572        self.mantissa.is_zero() && self.exponent.is_zero()
573    }
574}
575
576impl num_traits::One for StandardForm {
577    fn one() -> Self {
578        Self::new_unchecked(Finite::one(), num_traits::Zero::zero())
579    }
580}
581
582impl num_traits::Num for StandardForm {
583    type FromStrRadixErr = crate::ParsingStandardFormError;
584
585    fn from_str_radix(s: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
586        match radix != 10 {
587            true => Err(crate::ParsingStandardFormError::InvalidRadix),
588            false => Self::try_from(s),
589        }
590    }
591}
592
593impl num_traits::Signed for StandardForm {
594    #[must_use]
595    fn abs(&self) -> Self {
596        Self::new_unchecked(self.mantissa().abs(), *self.exponent())
597    }
598
599    #[must_use]
600    fn abs_sub(&self, other: &Self) -> Self {
601        match *self <= *other {
602            true => Self::zero(),
603            false => self.clone() - other.clone(),
604        }
605    }
606
607    #[must_use]
608    fn signum(&self) -> Self {
609        match self.mantissa().into_inner().signum() as i8 {
610            1 => Self::one(),
611            0 => Self::zero(),
612            _ => -Self::one(),
613        }
614    }
615
616    #[must_use]
617    fn is_positive(&self) -> bool {
618        self.mantissa().is_sign_positive()
619    }
620
621    #[must_use]
622    fn is_negative(&self) -> bool {
623        self.mantissa().is_sign_negative()
624    }
625}
626
627impl num_traits::FromPrimitive for StandardForm {
628    // Required methods
629    #[must_use]
630    fn from_i64(n: i64) -> Option<Self> {
631        Some(Self::from(n))
632    }
633    #[must_use]
634    fn from_u64(n: u64) -> Option<Self> {
635        Some(Self::from(n))
636    }
637
638    fn from_f64(n: f64) -> Option<Self> {
639        Self::try_from(n).ok()
640    }
641
642    fn from_f32(n: f32) -> Option<Self> {
643        Self::try_from(n).ok()
644    }
645}
646
647impl num_traits::ToPrimitive for StandardForm {
648    fn to_f64(&self) -> Option<f64> {
649        10i8.checked_pow(self.exponent as u32)
650            .map(|v: i8| self.mantissa.into_inner() * (v as f64))
651    }
652
653    fn to_i64(&self) -> Option<i64> {
654        self.to_f64().and_then(|val| {
655            // Check for special cases (NaN, Inf, etc.)
656            if val.is_nan() || val.is_infinite() {
657                return None;
658            }
659
660            // Check if the value is within the valid range for i64
661            if val < i64::MIN as f64 || val > i64::MAX as f64 {
662                return None;
663            }
664
665            // Convert safely
666            Some(val as i64)
667        })
668    }
669
670    fn to_u64(&self) -> Option<u64> {
671        self.to_i64().and_then(|val| val.try_into().ok())
672    }
673}
674
675const FIRST_SMALLER_VALUE_THEN_10: f64 = 10.0 - f64::EPSILON;
676impl num_traits::Bounded for StandardForm {
677    fn max_value() -> Self {
678        Self::new_unchecked(FIRST_SMALLER_VALUE_THEN_10.into(), i8::MAX)
679    }
680
681    fn min_value() -> Self {
682        Self::new_unchecked((-FIRST_SMALLER_VALUE_THEN_10).into(), i8::MIN)
683    }
684}
685
686#[cfg(test)]
687mod tests {
688    use super::*;
689    use test_case::test_case;
690
691    const FALIURE_MSG: &str = "Failed parsing standardform";
692
693    #[test_case( 1.0, 2, "1.00e+2"; "already_in_scientific_notation")]
694    #[test_case( 0.01, 4, "1.00e+2"; "small_number_adjustment")]
695    #[test_case( 1234.0, 0, "1.234e+3"; "large_number_adjustment")]
696    #[test_case( -5678.9, 2, "-5.6789e+5"; "negative_number_adjustment")]
697    #[test_case( 0.000567, -3, "5.67e-7"; "small_negative_exponent_adjustment")]
698    #[test_case( 98765.432, 3, "9.8765432e+7"; "large_mantissa_adjustment")]
699    #[test_case( 0.99999, -1, "9.9999e-2"; "rounding_edge_case")]
700    #[test_case( 1.5, 10, "1.50e+10"; "zero_adjustment_required")]
701    #[test_case( -0.0001, 2, "-1.00e-2"; "negative_small_number_adjustment")]
702    #[test_case( 999.999, -5, "9.99999e-3"; "high_precision_adjustment")]
703    fn correct_adjustment(mantissa: f64, exponent: i8, expected_in_science_notation: &str) {
704        assert_eq!(
705            StandardForm::new(Finite::from_inner(mantissa), exponent).to_scientific_notation(),
706            StandardForm::try_from(expected_in_science_notation)
707                .expect(FALIURE_MSG)
708                .to_scientific_notation()
709        )
710    }
711
712    #[test_case("1.0e2", "1.00e+2"; "parse_correct_scientific_notation")]
713    #[test_case("12345.678", "1.2345678e+4"; "parse_large_number")]
714    #[test_case("0.000567", "5.67e-4"; "parse_small_number")]
715    #[test_case("1.2345e3", "1.2345e+3"; "parse_exponent_included")]
716    #[test_case("-5678.9", "-5.6789e+3"; "parse_negative_number")]
717    #[test_case("0.00000123", "1.23e-6"; "parse_tiny_number")]
718    #[test_case("9999999.0", "9.999999e6"; "parse_large_integer")]
719    #[test_case("-0.005", "-5.00e-3"; "parse_negative_small_number")]
720    #[test_case("98765e1", "9.8765e+5"; "parse_large_scientific")]
721    #[test_case("0.0012345678", "1.2345678e-3"; "parse_high_precision")]
722    fn parsing_standardform(input: &str, expected_in_science_notation: &str) {
723        assert_eq!(
724            StandardForm::try_from(input).map(|v| v.to_scientific_notation()),
725            Ok(StandardForm::try_from(expected_in_science_notation)
726                .expect(FALIURE_MSG)
727                .to_scientific_notation())
728        )
729    }
730
731    #[test_case("1.0e2", "2.0e2", "3.00e+2"; "addition_simple")]
732    #[test_case("1.5e3", "2.5e3", "4.00e+3"; "addition_large_numbers")]
733    #[test_case("1.0e-2", "2.0e-2", "3.00e-2"; "addition_small_numbers")]
734    #[test_case("-1.0e3", "2.0e3", "1.00e+3"; "addition_with_negative")]
735    #[test_case("3.5e4", "4.5e4", "8.00e+4"; "addition_similar_exponent")]
736    #[test_case("5.0e1", "5.0e2", "5.50e+2"; "addition_different_exponent")]
737    #[test_case("1.23e2", "-2.46e2", "-1.23e+2"; "addition_negative_result")]
738    #[test_case("1.0e6", "1.0e6", "2.00e+6"; "addition_large_equal_numbers")]
739    #[test_case("5.0e-5", "5.0e-5", "1.00e-4"; "addition_small_equal_numbers")]
740    #[test_case("9.9e1", "1.1e1", "1.10e+2"; "addition_with_carry")]
741    fn add_standardform(lhs: &str, rhs: &str, expected_in_science_notation: &str) {
742        let lhs = StandardForm::try_from(lhs).expect(FALIURE_MSG);
743        let rhs = StandardForm::try_from(rhs).expect(FALIURE_MSG);
744
745        assert_eq!(
746            (lhs + rhs).to_scientific_notation(),
747            StandardForm::try_from(expected_in_science_notation)
748                .expect(FALIURE_MSG)
749                .to_scientific_notation()
750        )
751    }
752
753    #[test_case("3.0e2", "2.0e2", "1.00e+2"; "subtraction_simple")]
754    #[test_case("5.0e3", "2.0e3", "3.00e+3"; "subtraction_large_numbers")]
755    #[test_case("2.0e-2", "1.0e-2", "1.00e-2"; "subtraction_small_numbers")]
756    #[test_case("1.0e6", "1.0e5", "9.00e+5"; "subtraction_large_difference")]
757    #[test_case("7.0e2", "3.0e2", "4.00e+2"; "subtraction_with_carry")]
758    #[test_case("1.23e2", "-2.46e2", "3.69e+2"; "subtraction_negative_term")]
759    #[test_case("5.0e-3", "1.0e-3", "4.00e-3"; "subtraction_small_difference")]
760    #[test_case("1.0e2", "9.0e1", "1.00e+1"; "subtraction_near_equal")]
761    #[test_case("-1.0e3", "-1.0e2", "-9.00e+2"; "subtraction_negative_numbers")]
762    #[test_case("1.0e1", "5.0e0", "5.00e+0"; "subtraction_half_value")]
763    fn sub_standardform(lhs: &str, rhs: &str, expected_in_science_notation: &str) {
764        let lhs = StandardForm::try_from(lhs).expect(FALIURE_MSG);
765        let rhs = StandardForm::try_from(rhs).expect(FALIURE_MSG);
766
767        assert_eq!(
768            (lhs - rhs).to_scientific_notation(),
769            StandardForm::try_from(expected_in_science_notation)
770                .expect(FALIURE_MSG)
771                .to_scientific_notation()
772        )
773    }
774
775    #[test_case("1.0e2", "2.0e2", "5.00e-1"; "division_simple")]
776    #[test_case("5.0e3", "2.0e3", "2.50e+0"; "division_large_numbers")]
777    #[test_case("2.0e-2", "1.0e-2", "2.00e+0"; "division_small_numbers")]
778    #[test_case("1.0e6", "1.0e3", "1.00e+3"; "division_large_by_large")]
779    #[test_case("9.0e2", "3.0e2", "3.00e+0"; "division_exact_division")]
780    #[test_case("7.0e5", "1.0e3", "7.00e+2"; "division_no_rounding")]
781    #[test_case("8.0e-4", "2.0e-2", "4.00e-2"; "division_small_by_small")]
782    #[test_case("5.0e2", "5.0e1", "1.00e+1"; "division_by_tenth")]
783    #[test_case("1.0e1", "2.0e1", "5.00e-1"; "division_half")]
784    #[test_case("1.0e-1", "5.0e-1", "2.00e-1"; "division_small_numbers_with_remainder")]
785    fn div_standardform(lhs: &str, rhs: &str, expected_in_science_notation: &str) {
786        let lhs = StandardForm::try_from(lhs).expect(FALIURE_MSG);
787        let rhs = StandardForm::try_from(rhs).expect(FALIURE_MSG);
788
789        assert_eq!(
790            (lhs / rhs).to_scientific_notation(),
791            StandardForm::try_from(expected_in_science_notation)
792                .expect(FALIURE_MSG)
793                .to_scientific_notation()
794        )
795    }
796
797    #[test_case("1.0e2", "2.0e2", "2.00e+4"; "multiplication_simple")]
798    #[test_case("5.0e3", "2.0e3", "1.00e+7"; "multiplication_large_numbers")]
799    #[test_case("2.0e-2", "1.0e-2", "2.00e-4"; "multiplication_small_numbers")]
800    #[test_case("1.0e6", "1.0e-3", "1.00e+3"; "multiplication_large_by_small")]
801    #[test_case("9.0e2", "3.0e2", "2.70e+5"; "multiplication_no_rounding")]
802    #[test_case("7.0e5", "1.0e3", "7.00e+8"; "multiplication_large_by_large")]
803    #[test_case("8.0e-4", "2.0e-2", "1.60e-5"; "multiplication_small_by_small")]
804    #[test_case("5.0e2", "5.0e1", "2.50e+4"; "multiplication_by_tenth")]
805    #[test_case("1.0e1", "2.0e1", "2.00e+2"; "multiplication_basic")]
806    #[test_case("1.0e-1", "5.0e-1", "5.00e-2"; "multiplication_small_numbers2")]
807    fn mul_standardform(lhs: &str, rhs: &str, expected_in_science_notation: &str) {
808        let lhs = StandardForm::try_from(lhs).expect(FALIURE_MSG);
809        let rhs = StandardForm::try_from(rhs).expect(FALIURE_MSG);
810
811        assert_eq!(
812            (lhs * rhs).to_scientific_notation(),
813            StandardForm::try_from(expected_in_science_notation)
814                .expect(FALIURE_MSG)
815                .to_scientific_notation()
816        )
817    }
818
819    // TODO - Ensure these tests pass
820    #[test_case("5.0e2", "1.0e2", "0.00e+0"; "remainder_simple_whole")]
821    #[test_case("5.5e2", "1.0e2", "5.00e+1"; "remainder_simple_fraction")]
822    #[test_case("1.0e5", "3.0e4", "1.00e+4"; "remainder_large_numbers")]
823    #[test_case("1.0e2", "3.0e1", "1.00e+1"; "remainder_large_by_smaller")]
824    #[test_case("7.0e5", "4.0e5", "3.00e+5"; "remainder_no_rounding")]
825    #[test_case("1.2e-1", "5.0e-2", "2.00e-2"; "remainder_small_numbers")]
826    #[test_case("9.9e1", "2.0e1", "1.90e+1"; "remainder_with_fraction")]
827    #[test_case("5.0e3", "1.5e3", "5.00e+2"; "remainder_fraction_result")]
828    #[test_case("4.5e4", "1.0e4", "5.00e+3"; "remainder_exact_half")]
829    #[test_case("2.3e-1", "1.1e-1", "1.00e-2"; "remainder_small_fraction")]
830    fn rem_standardform(lhs: &str, rhs: &str, expected_in_science_notation: &str) {
831        let lhs = StandardForm::try_from(lhs).expect(FALIURE_MSG);
832        let rhs = StandardForm::try_from(rhs).expect(FALIURE_MSG);
833
834        assert_eq!(
835            (lhs % rhs).to_scientific_notation(),
836            StandardForm::try_from(expected_in_science_notation)
837                .expect(FALIURE_MSG)
838                .to_scientific_notation()
839        )
840    }
841}